UNPKG

@kamino-finance/klend-sdk

Version:

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

816 lines 130 kB
"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