UNPKG

@kamino-finance/klend-sdk

Version:

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

878 lines (877 loc) 102 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KaminoAction = exports.DEPOSITS_LIMIT = exports.BORROWS_LIMIT = exports.POSITION_LIMIT = void 0; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const bn_js_1 = __importDefault(require("bn.js")); const decimal_js_1 = __importDefault(require("decimal.js")); const instructions_1 = require("../idl_codegen/instructions"); const utils_1 = require("../utils"); const obligation_1 = require("./obligation"); const types_1 = require("../idl_codegen/types"); const farms_sdk_1 = require("@kamino-finance/farms-sdk"); const ObligationType_1 = require("../utils/ObligationType"); const lib_1 = require("../lib"); const scope_sdk_1 = require("@kamino-finance/scope-sdk"); exports.POSITION_LIMIT = 10; exports.BORROWS_LIMIT = 5; exports.DEPOSITS_LIMIT = 8; const SOL_PADDING_FOR_INTEREST = new bn_js_1.default('1000000'); class KaminoAction { kaminoMarket; reserve; outflowReserve; owner; payer; obligation = null; referrer; userTokenAccountAddress; userCollateralAccountAddress; additionalTokenAccountAddress; /** * Null unless the obligation is not passed */ obligationType = null; mint; secondaryMint; positions; amount; outflowAmount; computeBudgetIxs; computeBudgetIxsLabels; setupIxs; setupIxsLabels; inBetweenIxs; inBetweenIxsLabels; lendingIxs; lendingIxsLabels; cleanupIxs; cleanupIxsLabels; preTxnIxs; preTxnIxsLabels; postTxnIxs; postTxnIxsLabels; refreshFarmsCleanupTxnIxs; refreshFarmsCleanupTxnIxsLabels; depositReserves; borrowReserves; preLoadedDepositReservesSameTx; preLoadedBorrowReservesSameTx; currentSlot; constructor(kaminoMarket, owner, obligation, userTokenAccountAddress, userCollateralAccountAddress, mint, positions, amount, depositReserves, borrowReserves, reserveState, currentSlot, secondaryMint, additionalTokenAccountAddress, outflowReserveState, outflowAmount, referrer, payer) { if (obligation instanceof obligation_1.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_js_1.default(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_js_1.default(outflowAmount) : undefined; this.preLoadedDepositReservesSameTx = []; this.preLoadedBorrowReservesSameTx = []; this.referrer = referrer ? referrer : web3_js_1.PublicKey.default; this.currentSlot = currentSlot; } static async initialize(action, amount, mint, owner, kaminoMarket, obligation, referrer = web3_js_1.PublicKey.default, currentSlot = 0, payer) { 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); } static getUserAccountAddresses(owner, reserve) { const userTokenAccountAddress = (0, utils_1.getAssociatedTokenAddress)(reserve.liquidity.mintPubkey, owner, true, reserve.liquidity.tokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); const userCollateralAccountAddress = (0, utils_1.getAssociatedTokenAddress)(reserve.collateral.mintPubkey, owner, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); 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 = 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 utils_1.PublicKeySet([ ...borrowReserves.map((e) => e), ...(action === 'borrow' ? [reserve] : []), ...(action === 'depositAndBorrow' ? [reserve] : []), ]).toArray().length + new utils_1.PublicKeySet([ ...depositReserves.map((e) => e), ...(action === 'deposit' ? [reserve] : []), ...(action === 'depositAndBorrow' ? [outflowReserve] : []), ]).toArray().length; if (distinctReserveCount > exports.POSITION_LIMIT) { throw Error(`Obligation already has max number of positions: ${exports.POSITION_LIMIT}`); } return { kaminoObligation, depositReserves, borrowReserves, distinctReserveCount, }; } static async buildRefreshObligationTxns(kaminoMarket, payer, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn currentSlot = 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, payer, obligation, elevationGroup, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn currentSlot = 0) { const firstReserve = obligation.state.deposits.find((x) => !x.depositReserve.equals(web3_js_1.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, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas, requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, currentSlot = 0, scopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }, 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.addComputeBudgetIxn(extraComputeBudget); } const allReserves = new utils_1.PublicKeySet([ ...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, reserves) { const tokenIds = []; 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(web3_js_1.PublicKey.default)) { reserve.state.config.tokenInfo.scopeConfiguration.priceChain.map((x) => { if (x !== scope_sdk_1.U16_MAX) { tokenIds.push(x); } }); reserve.state.config.tokenInfo.scopeConfiguration.twapChain.map((x) => { if (x !== scope_sdk_1.U16_MAX) { tokenIds.push(x); } }); } } return tokenIds; } async addScopeRefreshIxs(tokens, feed = 'hubble') { this.preTxnIxsLabels.unshift(`refreshScopePrices`); this.preTxnIxs.unshift(await this.kaminoMarket.scope.refreshPriceListIx({ feed: feed, }, tokens)); } static async buildBorrowTxns(kaminoMarket, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas, requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, currentSlot = 0, scopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }, 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.addComputeBudgetIxn(extraComputeBudget); } const allReserves = new utils_1.PublicKeySet([ ...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, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata, referrer = web3_js_1.PublicKey.default, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, depositAmount, depositMint, borrowAmount, borrowMint, payer, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas, requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata, referrer = web3_js_1.PublicKey.default, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, repayAmount, repayMint, withdrawAmount, withdrawMint, payer, currentSlot, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas, requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata, isClosingPosition = false, referrer = web3_js_1.PublicKey.default, 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 utils_1.PublicKeySet([ ...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, amount, mint, owner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = true, // if true it includes create and close wsol and token atas, requestElevationGroup = false, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, amount, mint, owner, obligation, currentSlot, payer = undefined, extraComputeBudget = 1_000_000, includeAtaIxns = true, requestElevationGroup = false, includeUserMetadata = true, referrer = web3_js_1.PublicKey.default, 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 utils_1.PublicKeySet([ ...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, amount, minCollateralReceiveAmount, repayTokenMint, withdrawTokenMint, liquidator, obligationOwner, obligation, extraComputeBudget = 1_000_000, // if > 0 then adds the ixn includeAtaIxns = 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, includeUserMetadata = true, // if true it includes user metadata referrer = web3_js_1.PublicKey.default, maxAllowedLtvOverridePercent = 0, currentSlot = 0, 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 utils_1.PublicKeySet([ ...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, tokenMint, kaminoMarket, currentSlot = 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: null, lendingTxn: null, postLendingTxn: null, }; if (this.preTxnIxs.length) { txns.preLendingTxn = new web3_js_1.Transaction({ feePayer: this.owner, recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash, }).add(...this.preTxnIxs); } if (this.lendingIxs.length === 2) { txns.lendingTxn = new web3_js_1.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 web3_js_1.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 web3_js_1.Transaction({ feePayer: this.owner, recentBlockhash: (await this.kaminoMarket.getConnection().getLatestBlockhash()).blockhash, }).add(...this.postTxnIxs); } return txns; } async sendTransactions(sendTransaction) { 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; } async sendSingleTransaction(txn, sendTransaction) { if (!txn) return ''; const signature = await sendTransaction(txn, this.kaminoMarket.getConnection()); await this.kaminoMarket.getConnection().confirmTransaction(signature); return signature; } async simulateTransactions(sendTransaction) { 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; } async simulateSingleTransaction(txn, sendTransaction) { if (!txn) return ''; return await sendTransaction(txn, this.kaminoMarket.getConnection()); } addDepositIx() { this.lendingIxsLabels.push(`depositReserveLiquidityAndObligationCollateral`); this.lendingIxs.push((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY, }, this.kaminoMarket.programId)); } addDepositReserveLiquidityIx() { this.lendingIxsLabels.push(`depositReserveLiquidity`); this.lendingIxs.push((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY, }, this.kaminoMarket.programId)); } addRedeemReserveCollateralIx() { this.lendingIxsLabels.push(`redeemReserveCollateral`); this.lendingIxs.push((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY, }, this.kaminoMarket.programId)); } addDepositObligationCollateralIx() { this.lendingIxsLabels.push(`depositObligationCollateral`); this.lendingIxs.push((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, instructionSysvarAccount: web3_js_1.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 = (0, instructions_1.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: (0, utils_1.referrerTokenStatePda)(this.referrer, this.reserve.address, this.kaminoMarket.programId)[0], tokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.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((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.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 = (0, instructions_1.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: (0, utils_1.referrerTokenStatePda)(this.referrer, this.outflowReserve.address, this.kaminoMarket.programId)[0], tokenProgram: this.outflowReserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.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 = (0, instructions_1.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: web3_js_1.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((0, instructions_1.withdrawObligationCollateralAndRedeemReserveCollateral)({ collateralAmount: this.outflowAmount.eq(new bn_js_1.default(utils_1.U64_MAX)) ? this.outflowAmount : new bn_js_1.default(new decimal_js_1.default(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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.outflowReserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.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_js_1.default(utils_1.U64_MAX)) ? this.amount : new bn_js_1.default(new decimal_js_1.default(this.amount.toString()).mul(collateralExchangeRate).ceil().toString()); this.lendingIxsLabels.push(`withdrawObligationCollateralAndRedeemReserveCollateral`); this.lendingIxs.push((0, instructions_1.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: spl_token_1.TOKEN_PROGRAM_ID, liquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.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 = (0, instructions_1.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: web3_js_1.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 = 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) => { return { pubkey: reserve, isSigner: false, isWritable: true }; }); const liquidateIx = (0, instructions_1.liquidateObligationAndRedeemReserveCollateral)({ liquidityAmount: this.amount, // TODO: Configure this when updating liquidator with new interface minAcceptableReceivedLiquidityAmount: this.outflowAmount || new bn_js_1.default(0), maxAllowedLtvOverridePercent: new bn_js_1.default(maxAllowedLtvOverridePercent), }, { liquidator: this.owner, obligation: this.getObligationPda(), lendingMarket: this.kaminoMarket.getAddress(), lendingMarketAuthority: this.kaminoMarket.getLendingMarketAuthority(), repayReserve: this.reserve.address, repayReserveLiquidityMint: this.reserve.getLiquidityMint(), repayReserveLiquiditySupply: this.reserve.state.liquidity.supplyVault, withdrawReserve: this.outflowReserve.address, withdrawReserveLiquidityMint: this.outflowReserve.getLiquidityMint(), withdrawReserveCollateralMint: this.outflowReserve.getCTokenMint(), withdrawReserveCollateralSupply: this.outflowReserve.state.collateral.supplyVault, withdrawReserveLiquiditySupply: this.outflowReserve.state.liquidity.supplyVault, userSourceLiquidity: this.additionalTokenAccountAddress, userDestinationCollateral: this.userCollateralAccountAddress, userDestinationLiquidity: this.userTokenAccountAddress, withdrawReserveLiquidityFeeReceiver: this.outflowReserve.state.liquidity.feeVault, collateralTokenProgram: spl_token_1.TOKEN_PROGRAM_ID, repayLiquidityTokenProgram: this.reserve.getLiquidityTokenProgram(), withdrawLiquidityTokenProgram: this.outflowReserve.getLiquidityTokenProgram(), instructionSysvarAccount: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY, }, this.kaminoMarket.programId); liquidateIx.keys = this.obligation.state.elevationGroup > 0 ? liquidateIx.keys.concat([...depositReserveAccountMetas]) : liquidateIx.keys; this.lendingIxs.push(liquidateIx); } async addInBetweenIxs(action, includeAtaIxns, requestElevationGroup, addInitObligationForFarm, isClosingPosition = false) { await this.addSupportIxsWithoutInitObligation(action, includeAtaIxns, 'inBetween', requestElevationGroup, addInitObligationForFarm, isClosingPosition); } addRefreshObligation(crank) { const uniqueReserveAddresses = new utils_1.PublicKeySet(this.depositReserves.concat(this.borrowReserves)).toArray(); const addAllToSetupIxns = 'setup'; // Union of addresses const allReservesExcludingCurrent = [...uniqueReserveAddresses]; this.addRefreshReserveIxs(allReservesExcludingCurrent, addAllToSetupIxns); this.addRefreshFarmsForReserve(this.depositReserves.map((r) => this.kaminoMarket.getReserveByAddress(r)), addAllToSetupIxns, types_1.ReserveFarmKind.Collateral, crank); this.addRefreshFarmsForReserve(this.borrowReserves.map((r) => this.kaminoMarket.getReserveByAddress(r)), addAllToSetupIxns, types_1.ReserveFarmKind.Debt, crank); this.addRefreshObligationIx(addAllToSetupIxns, false); } async addSupportIxsWithoutInitObligation(action, includeAtaIxns, addAsSupportIx = 'setup', requestElevationGroup = false, addInitObligationForFarm = false, isClosingPosition = false, twoTokenAction = false, overrideElevationGroupRequest) { // TODO: why are we not doing this first? if (includeAtaIxns) { await this.addAtaIxs(action); } if ([ 'depositCollateral', 'deposit', 'withdraw', 'borrow', 'liquidate', 'repay', 'depositAndBorrow', 'repayAndWithdraw', 'refreshObligation', ].includes(action)) { // The support ixns in order are: // 0. Init obligation ixn // 0. Token Ata ixns // 0. Init obligation for farm // 1. Ixns to refresh the reserves of the obligation not related to the current action // 2. Ixn to refresh the reserve of the current action // 3. Ixn to refresh the obligation