@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
816 lines • 130 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const dotenv_1 = __importDefault(require("dotenv"));
const commander_1 = require("commander");
const kit_1 = require("@solana/kit");
const lib_1 = require("../lib");
const types_1 = require("../@codegen/klend/types");
const fraction_1 = require("../classes/fraction");
const decimal_js_1 = __importDefault(require("decimal.js"));
const bn_js_1 = __importDefault(require("bn.js"));
const types_2 = require("../@codegen/kvault/types");
const accounts_1 = require("../@codegen/kvault/accounts");
const vault_1 = require("../classes/vault");
const api_1 = require("../utils/api");
const fs = __importStar(require("fs"));
const VaultConfigField_1 = require("../@codegen/kvault/types/VaultConfigField");
const rpc_1 = require("../utils/rpc");
const token_2022_1 = require("@solana-program/token-2022");
const ManagerEnv_1 = require("./tx/ManagerEnv");
const processor_1 = require("./tx/processor");
const priorityFee_1 = require("../client/tx/priorityFee");
const address_lookup_table_1 = require("@solana-program/address-lookup-table");
const signer_1 = require("../utils/signer");
dotenv_1.default.config({
path: `.env${process.env.ENV ? '.' + process.env.ENV : ''}`,
});
async function main() {
const commands = new commander_1.Command();
commands.name('kamino-manager-cli').description('CLI to interact with the kvaults and klend programs');
commands
.command('create-market')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ mode, staging, devnet, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const admin = await env.getSigner();
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const { market: marketKp, ixs: createMarketIxs } = await kaminoManager.createMarketIxs({
admin,
});
await (0, processor_1.processTx)(env.c, admin, [
...createMarketIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
mode === 'execute' && console.log('Market created:', marketKp.address);
});
commands
.command('add-asset-to-market')
.requiredOption('--market <string>', 'Market address to add asset to')
.requiredOption('--mint <string>', 'Reserve liquidity token mint')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option('--global-admin <string>', 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode)')
.option('--reserve-key-path <string>', 'Path to the reserve key pair file')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ market, mint, reserveConfigPath, mode, staging, globalAdmin, multisig, reserveKeyPath }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms);
const tokenMint = (0, kit_1.address)(mint);
const marketAddress = (0, kit_1.address)(market);
const existingMarket = await lib_1.KaminoMarket.load(env.c.rpc, marketAddress, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, false);
if (existingMarket === null) {
throw new Error(`Market ${marketAddress} does not exist`);
}
const signer = await env.getSigner({ market: existingMarket });
const mintAccount = await (0, token_2022_1.fetchMint)(env.c.rpc, mint);
const tokenMintProgramId = mintAccount.programAddress;
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8'));
const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile);
const assetConfig = new lib_1.AssetReserveConfigCli(tokenMint, tokenMintProgramId, reserveConfig);
const [adminAta] = await (0, token_2022_1.findAssociatedTokenPda)({
mint: tokenMint,
owner: signer.address,
tokenProgram: tokenMintProgramId,
});
let globalAdminSigner = undefined;
if (globalAdmin) {
globalAdminSigner =
mode === 'multisig' ? (0, signer_1.noopSigner)((0, kit_1.address)(globalAdmin)) : await (0, signer_1.parseKeypairFile)(globalAdmin);
}
let reserveKeypair = undefined;
if (reserveKeyPath) {
reserveKeypair = await (0, signer_1.parseKeypairFile)(reserveKeyPath);
}
else {
reserveKeypair = await (0, kit_1.generateKeyPairSigner)();
}
const { createReserveIxs, configUpdateIxs } = await kaminoManager.addAssetToMarketIxs({
admin: signer,
adminLiquiditySource: adminAta,
marketAddress: marketAddress,
assetConfig: assetConfig,
reserveKeypair,
globalAdminSigner,
});
console.log('reserve: ', reserveKeypair.address);
await (0, processor_1.processTx)(env.c, signer, [
...createReserveIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
const [lut, createLutIxs] = await createUpdateReserveConfigLutIxs(env, marketAddress, reserveKeypair.address);
await (0, processor_1.processTx)(env.c, signer, [
...createLutIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode);
const lutAcc = await (0, address_lookup_table_1.fetchAddressLookupTable)(env.c.rpc, lut);
// Split config update instructions into chunks to avoid transaction size limits
const CHUNK_SIZE = 8;
for (let i = 0; i < configUpdateIxs.length; i += CHUNK_SIZE) {
const chunk = configUpdateIxs.slice(i, i + CHUNK_SIZE);
await (0, processor_1.processTx)(env.c, signer, [
...chunk.map((ix) => ix.ix),
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits: 400_000,
}),
], mode, [lutAcc]);
}
mode === 'execute' &&
console.log('Reserve Created with config:', JSON.parse(JSON.stringify(reserveConfig)), '\nreserve address:', reserveKeypair.address);
});
commands
.command('update-reserve-config')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option('--global-admin <string>', 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode)')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ reserve, reserveConfigPath, mode, staging, globalAdmin, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms);
const reserveAddress = (0, kit_1.address)(reserve);
const reserveState = await lib_1.Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (reserveState === null) {
throw new Error(`Reserve ${reserveAddress} not found`);
}
const marketAddress = reserveState.lendingMarket;
const marketState = await lib_1.KaminoMarket.load(env.c.rpc, marketAddress, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, false);
if (marketState === null) {
throw new Error(`Market ${marketAddress} not found`);
}
const signer = await env.getSigner({ market: marketState });
const marketWithAddress = {
address: marketAddress,
state: marketState.state,
};
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8'));
const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile);
const updateIxs = await kaminoManager.updateReserveIxs(signer, marketWithAddress, reserveAddress, reserveConfig, reserveState, globalAdmin);
// Split config update instructions into chunks to avoid transaction size limits
const CHUNK_SIZE = 8;
for (let i = 0; i < updateIxs.length; i += CHUNK_SIZE) {
const chunk = updateIxs.slice(i, i + CHUNK_SIZE);
await (0, processor_1.processTx)(env.c, signer, [
...chunk.map((ix) => ix.ix),
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits: 400_000,
}),
], mode, []);
}
mode === 'execute' && console.log('Reserve Updated with config -> ', JSON.parse(JSON.stringify(reserveConfig)));
});
commands
.command('download-reserve-config')
.requiredOption('--reserve <string>', 'Reserve address')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ reserve, staging }) => {
const env = await (0, ManagerEnv_1.initEnv)(undefined, staging);
const reserveAddress = (0, kit_1.address)(reserve);
const reserveState = await lib_1.Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
fs.mkdirSync('./configs/' + reserveState.lendingMarket, { recursive: true });
const decoder = new TextDecoder('utf-8');
const reserveName = decoder.decode(Uint8Array.from(reserveState.config.tokenInfo.name)).replace(/\0/g, '');
const reserveConfigDisplay = parseReserveConfigToFile(reserveState.config);
fs.writeFileSync('./configs/' + reserveState.lendingMarket + '/' + reserveName + '.json', JSON.stringify(reserveConfigDisplay, null, 2));
});
commands
.command('init-kvault-global-config')
.requiredOption('--mode <string>', 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option('--signer-path <string>', 'If set, it will use the provided signer')
.action(async ({ mode, staging, devnet, multisig, signerPath }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, signerPath, undefined, devnet);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
let signer = undefined;
if (signerPath) {
signer = await (0, signer_1.parseKeypairFile)(signerPath);
}
else {
const programData = await (0, lib_1.programDataPda)(env.kvaultProgramId);
const programDataInfo = await env.c.rpc.getAccountInfo(programData).send();
if (programDataInfo === null) {
throw new Error('KVault program data not found');
}
const programAdmin = programDataInfo.value?.owner.toString();
if (!programAdmin) {
throw new Error('Program admin not found');
}
signer = (0, signer_1.noopSigner)((0, kit_1.address)(programAdmin));
}
const ix = await kaminoManager.initKvaultGlobalConfigIx(signer);
await (0, processor_1.processTx)(env.c, signer, [
ix,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
mode === 'execute' && console.log('KVault global config initialized');
});
commands
.command('update-kvault-global-config')
.requiredOption('--mode <string>', 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--field <string>', 'The field to update')
.requiredOption('--value <string>', 'The value to update the field to')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option('--signer-path <string>', 'If set, it will use the provided signer')
.action(async ({ mode, field, value, staging, devnet, multisig, signerPath }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, signerPath, undefined, devnet);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
let signer = undefined;
if (signerPath) {
signer = await (0, signer_1.parseKeypairFile)(signerPath);
}
else {
const globalConfigAddress = await (0, lib_1.getKvaultGlobalConfigPda)(env.kvaultProgramId);
const globalConfigState = await lib_1.KVaultGlobalConfig.fetch(env.c.rpc, globalConfigAddress);
if (!globalConfigState) {
throw new Error('Global config not found');
}
signer = (0, signer_1.noopSigner)((0, kit_1.address)(globalConfigState.globalAdmin));
}
const vaultClient = kaminoManager.getKaminoVaultClient();
const ix = await vaultClient.updateGlobalConfigIx(field, value);
await (0, processor_1.processTx)(env.c, signer, [ix, ...(0, priorityFee_1.getPriorityFeeAndCuIxs)({ priorityFeeMultiplier: 2500 })], mode, []);
mode === 'execute' && console.log(`Global config updated to ${value} for field ${field}`);
});
commands
.command('accept-kvault-global-config-ownership')
.requiredOption('--mode <string>', 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option('--signer-path <string>', 'If set, it will use the provided signer')
.action(async ({ mode, staging, devnet, multisig, signerPath }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
let signer = undefined;
if (signerPath) {
signer = await (0, signer_1.parseKeypairFile)(signerPath);
}
else {
const globalConfigAddress = await (0, lib_1.getKvaultGlobalConfigPda)(env.kvaultProgramId);
const globalConfigState = await lib_1.KVaultGlobalConfig.fetch(env.c.rpc, globalConfigAddress);
if (!globalConfigState) {
throw new Error('Global config not found');
}
signer = (0, signer_1.noopSigner)((0, kit_1.address)(globalConfigState.pendingAdmin));
}
const vaultClient = kaminoManager.getKaminoVaultClient();
const ix = await vaultClient.acceptGlobalConfigOwnershipIx(signer);
await (0, processor_1.processTx)(env.c, signer, [ix, ...(0, priorityFee_1.getPriorityFeeAndCuIxs)({ priorityFeeMultiplier: 2500 })], mode, []);
mode === 'execute' && console.log('Global config ownership accepted');
});
commands
.command('create-vault')
.requiredOption('--mint <string>', 'Vault token mint')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--name <string>', 'The onchain name of the strat')
.requiredOption('--tokenName <string>', 'The name of the token in the vault')
.requiredOption('--extraTokenName <string>', 'The extra string appended to the token symbol')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ mint, mode, name, tokenName, extraTokenName, staging, devnet, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const tokenMint = (0, kit_1.address)(mint);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const admin = await env.getSigner();
const tokenProgramID = await (0, rpc_1.getAccountOwner)(env.c.rpc, tokenMint);
const kaminoVaultConfig = new lib_1.KaminoVaultConfig({
admin,
tokenMint: tokenMint,
tokenMintProgramId: tokenProgramID,
performanceFeeRatePercentage: new decimal_js_1.default(0.0),
managementFeeRatePercentage: new decimal_js_1.default(0.0),
name,
vaultTokenSymbol: tokenName,
vaultTokenName: extraTokenName,
});
const useDevnetFarms = devnet ? true : false;
const { vault: vaultKp, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig, useDevnetFarms);
await (0, processor_1.processTx)(env.c, admin, [
...instructions.createAtaIfNeededIxs,
...instructions.initVaultIxs,
instructions.createLUTIx,
instructions.setFarmToVaultIx,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
await (0, lib_1.sleep)(2000);
// create the farm
await (0, processor_1.processTx)(env.c, admin, [
...instructions.createVaultFarm.setupFarmIxs,
...instructions.createVaultFarm.updateFarmIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
await (0, lib_1.sleep)(2000);
await (0, processor_1.processTx)(env.c, admin, [
...instructions.populateLUTIxs,
...instructions.cleanupIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
await (0, processor_1.processTx)(env.c, admin, [
instructions.initSharesMetadataIx,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
}),
], mode, []);
mode === 'execute' && console.log('Vault created:', vaultKp.address);
});
commands
.command('set-shares-metadata')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--symbol <string>', 'The symbol of the kVault token')
.requiredOption('--extraName <string>', 'The name of the kVault token, appended to the symbol')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ vault, mode, symbol, extraName, staging, CU: cu }) => {
const env = await (0, ManagerEnv_1.initEnv)(undefined, staging);
const kVault = new lib_1.KaminoVault(env.c.rpc, (0, kit_1.address)(vault));
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const vaultState = await kVault.getState();
const signer = await env.getSigner({ vaultState });
const ix = await kaminoManager.getSetSharesMetadataIx(signer, kVault, symbol, extraName);
await (0, processor_1.processTx)(env.c, signer, [
ix,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
});
commands
.command('update-vault-pending-admin')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--new-admin <string>', 'Pubkey of the new admin')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ vault, newAdmin, mode, staging, devnet, CU: cu }) => {
const env = await (0, ManagerEnv_1.initEnv)(staging, undefined, undefined, undefined, devnet);
const vaultAddress = (0, kit_1.address)(vault);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const kaminoVault = new lib_1.KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new VaultConfigField_1.PendingVaultAdmin(), newAdmin, signer, undefined, true);
await (0, processor_1.processTx)(env.c, signer, [
instructions.updateVaultConfigIx,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
mode === 'execute' && console.log(`Pending admin updated to ${newAdmin}`);
});
commands
.command('update-vault-config')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--field <string>', 'The field to update')
.requiredOption('--value <string>', 'The value to update the field to')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--skip-lut-update`, 'If set, it will skip the LUT update')
.option(`--lutSigner <string>`, 'If set, it will use the provided signer instead of the default one for the LUT update')
.option(`--global-admin <string>`, 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode). Required when setting AllowInvestInWhitelistedReservesOnly or AllowAllocationsInWhitelistedReservesOnly to false')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--error-on-override`, 'If set, it will throw an error if the vault already has a farm, if you want to override it set errorOnOverride to false')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ vault, field, value, mode, staging, devnet, skipLutUpdate, lutSigner, globalAdmin, multisig, errorOnOverride, CU: cu, }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const vaultAddress = (0, kit_1.address)(vault);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId);
const kaminoVault = new lib_1.KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
// Use global admin signer if provided, otherwise fall back to vault admin/noop signer depending on mode
let signer;
if (mode === 'multisig' && globalAdmin) {
signer = (0, signer_1.noopSigner)((0, kit_1.address)(globalAdmin));
}
else if (globalAdmin) {
signer = await (0, signer_1.parseKeypairFile)(globalAdmin);
}
else {
signer = await env.getSigner({ vaultState });
}
let lutSignerOrUndefined = undefined;
if (lutSigner) {
lutSignerOrUndefined = await (0, signer_1.parseKeypairFile)(lutSigner);
}
const shouldSkipLutUpdate = !!skipLutUpdate;
const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, field, value, signer, lutSignerOrUndefined, shouldSkipLutUpdate, errorOnOverride);
await (0, processor_1.processTx)(env.c, signer, [
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
mode === 'execute' && console.log('Vault updated');
});
commands
.command('add-update-whitelisted-reserve')
.requiredOption('--reserve <string>', 'Reserve address to whitelist')
.requiredOption('--whitelist-mode <string>', 'Whitelist mode: "Invest" or "AddAllocation"')
.requiredOption('--value <string>', 'Value: "1" or "true" to add to whitelist, "0" or "false" to remove from whitelist')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--global-admin <string>', 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode)')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ reserve, whitelistMode, value, mode, globalAdmin, staging, devnet, multisig, CU: cu }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const reserveAddress = (0, kit_1.address)(reserve);
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
// Parse the value (1/true = add, 0/false = remove)
const flagValue = (0, lib_1.parseBooleanFlag)(value);
// Parse the whitelist mode
let whitelistModeEnum;
if (whitelistMode === 'Invest') {
whitelistModeEnum = new types_2.UpdateReserveWhitelistMode.Invest([flagValue]);
}
else if (whitelistMode === 'AddAllocation') {
whitelistModeEnum = new types_2.UpdateReserveWhitelistMode.AddAllocation([flagValue]);
}
else {
throw new Error(`Invalid whitelist mode '${whitelistMode}'. Expected 'Invest' or 'AddAllocation'.`);
}
let globalAdminSigner;
if (mode === 'multisig') {
globalAdminSigner = (0, signer_1.noopSigner)((0, kit_1.address)(globalAdmin));
}
else {
globalAdminSigner = await (0, signer_1.parseKeypairFile)(globalAdmin);
}
const instruction = await kaminoManager.addUpdateWhitelistedReserveIx(reserveAddress, whitelistModeEnum, globalAdminSigner);
await (0, processor_1.processTx)(env.c, globalAdminSigner, [
instruction,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
mode === 'execute' &&
console.log(`Reserve ${reserveAddress} whitelisted for ${whitelistMode} with value ${flagValue ? 'ALLOW' : 'DENY'}`);
});
commands
.command('whitelist-reserves')
.requiredOption('--reserves-file <string>', 'Path to a file containing newline-separated reserve addresses to whitelist')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--global-admin <string>', 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode)')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ reservesFile, mode, globalAdmin, staging, multisig, CU: cu }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const fileContent = fs.readFileSync(reservesFile, 'utf-8');
const reserveAddresses = fileContent
.split('\n')
.map((r) => r.trim())
.filter((r) => r.length > 0)
.map((r) => (0, kit_1.address)(r));
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
let globalAdminSigner;
if (mode === 'multisig') {
globalAdminSigner = (0, signer_1.noopSigner)((0, kit_1.address)(globalAdmin));
}
else {
globalAdminSigner = await (0, signer_1.parseKeypairFile)(globalAdmin);
}
const instructions = [];
for (const reserveAddress of reserveAddresses) {
let instruction = await kaminoManager.addUpdateWhitelistedReserveIx(reserveAddress, new types_2.UpdateReserveWhitelistMode.Invest([1]), globalAdminSigner);
instructions.push(instruction);
instruction = await kaminoManager.addUpdateWhitelistedReserveIx(reserveAddress, new types_2.UpdateReserveWhitelistMode.AddAllocation([1]), globalAdminSigner);
instructions.push(instruction);
}
// batch the instructions in groups of 6
const batchSize = 6;
const batchInstructions = [];
for (let i = 0; i < instructions.length; i += batchSize) {
batchInstructions.push(instructions.slice(i, i + batchSize));
}
for (const batch of batchInstructions) {
await (0, processor_1.processTx)(env.c, globalAdminSigner, [...batch, ...(0, priorityFee_1.getPriorityFeeAndCuIxs)({ priorityFeeMultiplier: 2500, computeUnits })], mode, []);
}
});
commands
.command('backfill-whitelisted-reserves')
.option('--value <string>', 'Value: "1" or "true" to add to whitelist, "0" or "false" to remove from whitelist', '1')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.requiredOption('--global-admin <string>', 'Global admin signer (keypair path in execute/simulate modes, pubkey in multisig mode)')
.option(`--markets <string>`, 'Comma-separated list of market addresses. If not provided, all markets will be processed')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ value, mode, globalAdmin, markets, staging, devnet, multisig, CU: cu }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? (0, kit_1.address)(multisig) : undefined;
const env = await (0, ManagerEnv_1.initEnv)(staging, ms, undefined, undefined, devnet);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
let globalAdminSigner;
if (mode === 'multisig') {
globalAdminSigner = (0, signer_1.noopSigner)((0, kit_1.address)(globalAdmin));
}
else {
globalAdminSigner = await (0, signer_1.parseKeypairFile)(globalAdmin);
}
// Get markets to process
let marketsToProcess;
if (markets) {
const marketAddresses = markets.split(',').map((m) => (0, kit_1.address)(m.trim()));
console.log(`Processing ${marketAddresses.length} specified markets...`);
marketsToProcess = await Promise.all(marketAddresses.map(async (marketAddress) => {
const market = await lib_1.KaminoMarket.load(env.c.rpc, marketAddress, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId);
if (!market) {
throw new Error(`Market ${marketAddress} not found`);
}
return market;
}));
}
else {
console.log('Fetching all markets...');
marketsToProcess = await kaminoManager.getAllMarkets(env.klendProgramId);
console.log(`Found ${marketsToProcess.length} markets`);
}
// Collect all reserves from all markets
const allReserves = [];
for (const market of marketsToProcess) {
const marketName = (0, lib_1.parseTokenSymbol)(market.state.name);
const reserveAddresses = Array.from(market.reserves.keys());
console.log(`Market ${market.getAddress()} (${marketName}): ${reserveAddresses.length} reserves`);
allReserves.push(...reserveAddresses);
}
console.log(`\nTotal reserves to whitelist: ${allReserves.length}`);
console.log(`Whitelist modes: Invest AND AddAllocation (both will be set)`);
// Parse the value (1/true = add, 0/false = remove)
const flagValue = (0, lib_1.parseBooleanFlag)(value);
console.log(`Action: ${flagValue ? 'ADD to whitelist' : 'REMOVE from whitelist'}`);
if (mode === 'simulate') {
console.log('\nSimulation mode - no transactions will be executed');
}
// Create whitelist mode enums for both Invest and AddAllocation
const investModeEnum = new types_2.UpdateReserveWhitelistMode.Invest([flagValue]);
const addAllocationModeEnum = new types_2.UpdateReserveWhitelistMode.AddAllocation([flagValue]);
// Process each reserve with both whitelist modes
let successCount = 0;
let errorCount = 0;
for (const reserveAddress of allReserves) {
try {
const investInstruction = await kaminoManager.addUpdateWhitelistedReserveIx(reserveAddress, investModeEnum, globalAdminSigner);
const addAllocationInstruction = await kaminoManager.addUpdateWhitelistedReserveIx(reserveAddress, addAllocationModeEnum, globalAdminSigner);
await (0, processor_1.processTx)(env.c, globalAdminSigner, [
investInstruction,
addAllocationInstruction,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
successCount++;
console.log(`✓ [${successCount}/${allReserves.length}] ${reserveAddress} (Invest + AddAllocation)`);
}
catch (error) {
errorCount++;
console.error(`✗ Failed to whitelist ${reserveAddress}:`, error);
}
}
console.log(`\nBackfill complete!`);
console.log(`Success: ${successCount} reserves (both modes set)`);
console.log(`Errors: ${errorCount}`);
});
commands
.command('is-reserve-whitelisted')
.requiredOption('--reserve <string>', 'Reserve address to check')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.action(async ({ reserve, staging, devnet }) => {
const env = await (0, ManagerEnv_1.initEnv)(staging, undefined, undefined, undefined, devnet);
const reserveAddress = (0, kit_1.address)(reserve);
const pda = await (0, vault_1.getReserveWhitelistEntryPda)(reserveAddress, env.kvaultProgramId);
const entry = await accounts_1.ReserveWhitelistEntry.fetch(env.c.rpc, pda, env.kvaultProgramId);
if (!entry) {
console.log(`Reserve ${reserveAddress}`);
console.log(` PDA: ${pda} (not initialized)`);
console.log(` whitelistInvest: 0`);
console.log(` whitelistAddAllocation: 0`);
}
else {
console.log(`Reserve ${reserveAddress}`);
console.log(` PDA: ${pda}`);
console.log(` tokenMint: ${entry.tokenMint}`);
console.log(` whitelistInvest: ${entry.whitelistInvest}`);
console.log(` whitelistAddAllocation: ${entry.whitelistAddAllocation}`);
}
});
commands
.command('check-whitelist-for-mint')
.requiredOption('--mint <string>', 'Token mint address to check across all UI markets')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.action(async ({ mint, staging, devnet }) => {
const env = await (0, ManagerEnv_1.initEnv)(staging, undefined, undefined, undefined, devnet);
const tokenMint = (0, kit_1.address)(mint);
const marketsConfig = await (0, api_1.getMarketsFromApi)({ api: { programId: env.klendProgramId } });
console.log(`Found ${marketsConfig.length} UI markets from CDN\n`);
const markets = await Promise.all(marketsConfig.map(async (cfg) => {
const market = await lib_1.KaminoMarket.load(env.c.rpc, (0, kit_1.address)(cfg.lendingMarket), lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId);
return { cfg, market };
}));
// Collect all reserves for this mint across all markets
const reserveEntries = [];
for (const { cfg, market } of markets) {
if (!market)
continue;
const reserve = market.getReserveByMint(tokenMint);
if (reserve) {
reserveEntries.push({
marketName: cfg.name,
marketAddress: cfg.lendingMarket,
reserveAddress: reserve.address,
symbol: reserve.symbol,
});
}
}
if (reserveEntries.length === 0) {
console.log(`No reserves found for mint ${tokenMint} in any UI market`);
return;
}
// Derive all PDAs and batch-fetch whitelist entries
const pdas = await Promise.all(reserveEntries.map((e) => (0, vault_1.getReserveWhitelistEntryPda)(e.reserveAddress, env.kvaultProgramId)));
const entries = await accounts_1.ReserveWhitelistEntry.fetchMultiple(env.c.rpc, pdas, env.kvaultProgramId);
let missingCount = 0;
for (let i = 0; i < reserveEntries.length; i++) {
const r = reserveEntries[i];
const entry = entries[i];
const pda = pdas[i];
const invest = entry ? entry.whitelistInvest : 0;
const addAlloc = entry ? entry.whitelistAddAllocation : 0;
const pdaStatus = entry ? 'initialized' : 'NOT initialized';
const isMissing = !entry || invest === 0 || addAlloc === 0;
if (isMissing) {
missingCount++;
console.log(`[MISSING] ${r.symbol} reserve ${r.reserveAddress} in market "${r.marketName}" (${r.marketAddress})`);
console.log(` PDA: ${pda} (${pdaStatus})`);
console.log(` whitelistInvest: ${invest}`);
console.log(` whitelistAddAllocation: ${addAlloc}`);
console.log('');
}
}
if (missingCount === 0) {
console.log(`All ${reserveEntries.length} reserves for mint ${tokenMint} are fully whitelisted (Invest + AddAllocation)`);
}
else {
console.log(`${missingCount}/${reserveEntries.length} reserves need whitelisting`);
}
});
commands
.command('update-vault-mgmt-fee')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--fee-bps <string>', 'Management fee to set (in basis points)')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--CU <number>`, 'The number of compute units to use for the transaction')
.action(async ({ vault, feeBps, mode, staging, devnet, CU: cu }) => {
const env = await (0, ManagerEnv_1.initEnv)(staging, undefined, undefined, undefined, devnet);
const vaultAddress = (0, kit_1.address)(vault);
const computeUnits = cu ? cu : lib_1.DEFAULT_CU_PER_TX;
const kaminoManager = new lib_1.KaminoManager(env.c.rpc, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.klendProgramId, env.kvaultProgramId, undefined, env.farmsProgramId);
const kaminoVault = new lib_1.KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new VaultConfigField_1.ManagementFeeBps(), feeBps, signer);
await (0, processor_1.processTx)(env.c, signer, [
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...(0, priorityFee_1.getPriorityFeeAndCuIxs)({
priorityFeeMultiplier: 2500,
computeUnits,
}),
], mode, []);
mode === 'execute' && console.log('Management fee updated');
});
commands
.command('insert-into-lut')
.requiredOption('--lut <string>', 'Lookup table address')
.requiredOption('--addresses <string>', 'The addresses to insert into the LUT, space separated')
.requiredOption(`--mode <string>`, 'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--devnet`, 'If true, will use devnet programs and RPC')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--signer <string>`, 'If set, it will use the provided signer instea