@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
787 lines • 54.9 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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__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 });
exports.parseKeypairFile = parseKeypairFile;
const dotenv_1 = __importDefault(require("dotenv"));
const commander_1 = require("commander");
const web3_js_1 = require("@solana/web3.js");
const lib_1 = require("./lib");
const anchor = __importStar(require("@coral-xyz/anchor"));
const base58_js_1 = require("base58-js");
const types_1 = require("./idl_codegen/types");
const fraction_1 = require("./classes/fraction");
const decimal_js_1 = __importDefault(require("decimal.js"));
const anchor_1 = require("@coral-xyz/anchor");
const types_2 = require("./idl_codegen_kamino_vault/types");
const fs = __importStar(require("fs"));
const VaultConfigField_1 = require("./idl_codegen_kamino_vault/types/VaultConfigField");
const rpc_1 = require("./utils/rpc");
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 - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const multisigPk = multisig ? new web3_js_1.PublicKey(multisig) : web3_js_1.PublicKey.default;
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const { market: marketKp, ixns: createMarketIxns } = await kaminoManager.createMarketIxs({
admin: mode === 'multisig' ? multisigPk : env.payer.publicKey,
});
const _createMarketSig = await processTxn(env.client, env.payer, createMarketIxns, mode, 2500, [marketKp]);
mode === 'execute' && console.log('Market created:', marketKp.publicKey.toBase58());
});
commands
.command('add-asset-to-market')
.requiredOption('--market <string>', 'Market address to add asset to')
.requiredOption('--mint <string>', 'Reserve liquidity token mint')
.requiredOption('--mint-program-id <string>', 'Reserve liquidity token mint program id')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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, mintProgramId, reserveConfigPath, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const tokenMint = new web3_js_1.PublicKey(mint);
const tokenMintProgramId = new web3_js_1.PublicKey(mintProgramId);
const marketAddress = new web3_js_1.PublicKey(market);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const multisigPk = multisig ? new web3_js_1.PublicKey(multisig) : web3_js_1.PublicKey.default;
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8'));
const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile);
const assetConfig = new lib_1.AssetReserveConfigCli(tokenMint, tokenMintProgramId, reserveConfig);
const { reserve, txnIxns } = await kaminoManager.addAssetToMarketIxs({
admin: mode === 'multisig' ? multisigPk : env.payer.publicKey,
marketAddress: marketAddress,
assetConfig: assetConfig,
});
console.log('reserve: ', reserve.publicKey);
const _createReserveSig = await processTxn(env.client, env.payer, txnIxns[0], mode, 2500, [reserve]);
const _updateReserveSig = await processTxn(env.client, env.payer, txnIxns[1], mode, 2500, [], 400_000);
mode === 'execute' &&
console.log('Reserve Created with config:', JSON.parse(JSON.stringify(reserveConfig)), '\nreserve address:', reserve.publicKey.toBase58());
});
commands
.command('update-reserve-config')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.option('--update-entire-config', 'If set, it will update entire reserve config in 1 instruction')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ reserve, reserveConfigPath, mode, updateEntireConfig, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const reserveAddress = new web3_js_1.PublicKey(reserve);
const reserveState = await lib_1.Reserve.fetch(env.connection, reserveAddress, env.kLendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
const marketAddress = reserveState.lendingMarket;
const marketState = await lib_1.LendingMarket.fetch(env.connection, marketAddress, env.kLendProgramId);
if (!marketState) {
throw new Error('Market not found');
}
const marketWithAddress = {
address: marketAddress,
state: marketState,
};
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8'));
const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile);
const ixns = await kaminoManager.updateReserveIxs(marketWithAddress, reserveAddress, reserveConfig, reserveState, updateEntireConfig);
const _updateReserveSig = await processTxn(env.client, env.payer, ixns, mode, 2500, [], 400_000);
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 = initializeClient(false, staging);
const reserveAddress = new web3_js_1.PublicKey(reserve);
const reserveState = await lib_1.Reserve.fetch(env.connection, reserveAddress, env.kLendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
fs.mkdirSync('./configs/' + reserveState.lendingMarket.toBase58(), { 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.toBase58() + '/' + reserveName + '.json', JSON.stringify(reserveConfigDisplay, null, 2));
});
commands
.command('create-vault')
.requiredOption('--mint <string>', 'Vault token mint')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ mint, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const tokenMint = new web3_js_1.PublicKey(mint);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const multisigPk = multisig ? new web3_js_1.PublicKey(multisig) : web3_js_1.PublicKey.default;
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const tokenProgramID = await (0, rpc_1.getAccountOwner)(env.connection, tokenMint);
const kaminoVaultConfig = new lib_1.KaminoVaultConfig({
admin: mode === 'multisig' ? multisigPk : env.payer.publicKey,
tokenMint: tokenMint,
tokenMintProgramId: tokenProgramID,
performanceFeeRate: new decimal_js_1.default(0.0),
managementFeeRate: new decimal_js_1.default(0.0),
});
const { vault: vaultKp, ixns: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);
const _createVaultSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [vaultKp]);
mode === 'execute' && console.log('Vault created:', vaultKp.publicKey.toBase58());
});
commands
.command('update-vault-pending-admin')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--new-admin <string>', 'Pubkey of the new admin')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, newAdmin, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new VaultConfigField_1.PendingVaultAdmin(), newAdmin);
const updateVaultPendingAdminSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
mode === 'execute' && console.log('Pending admin updated:', updateVaultPendingAdminSig);
});
commands
.command('update-vault-mgmt-fee')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--fee-bps <string>', 'Pubkey of the new admin')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, feeBps, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new VaultConfigField_1.ManagementFeeBps(), feeBps);
const updateVaultConfigSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
mode === 'execute' && console.log('Management fee updated:', updateVaultConfigSig);
});
commands
.command('update-vault-perf-fee')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--fee-bps <string>', 'Pubkey of the new admin')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, feeBps, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new VaultConfigField_1.PerformanceFeeBps(), feeBps);
const updateVaultPerfFeeSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
mode === 'execute' && console.log('Performance fee updated:', updateVaultPerfFeeSig);
});
commands
.command('accept-vault-ownership')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instruction = await kaminoManager.acceptVaultOwnershipIx(kaminoVault);
const acceptVaultOwnershipSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
mode === 'execute' && console.log('Vault ownership accepted:', acceptVaultOwnershipSig);
});
commands
.command('give-up-pending-fees')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--max-amount-to-give-up <string>', 'Max amount to give up')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, maxAmountToGiveUp, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instruction = await kaminoManager.giveUpPendingFeesIx(kaminoVault, new decimal_js_1.default(maxAmountToGiveUp));
const giveUpPendingFeesSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
mode === 'execute' && console.log('Give up pending fees tx:', giveUpPendingFeesSig);
});
commands
.command('withdraw-pending-fees')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instructions = await kaminoManager.withdrawPendingFeesIxs(kaminoVault, await env.connection.getSlot('confirmed'));
const withdrawPendingFeesSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
mode === 'execute' && console.log('Pending fees withdrawn:', withdrawPendingFeesSig);
});
commands
.command('update-vault-reserve-allocation')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption('--allocation-weight <number>', 'Allocation weight')
.requiredOption('--allocation-cap <string>', 'Allocation cap decimal value')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, reserve, allocationWeight, allocationCap, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const reserveAddress = new web3_js_1.PublicKey(reserve);
const vaultAddress = new web3_js_1.PublicKey(vault);
const allocationWeightValue = Number(allocationWeight);
const allocationCapDecimal = new decimal_js_1.default(allocationCap);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const reserveState = await lib_1.Reserve.fetch(env.connection, reserveAddress, env.kLendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
const reserveWithAddress = {
address: reserveAddress,
state: reserveState,
};
const firstReserveAllocationConfig = new lib_1.ReserveAllocationConfig(reserveWithAddress, allocationWeightValue, allocationCapDecimal);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instructions = await kaminoManager.updateVaultReserveAllocationIxs(kaminoVault, firstReserveAllocationConfig);
const updateVaultAllocationSig = await processTxn(env.client, env.payer, [instructions], mode, 2500, []);
mode === 'execute' && console.log('Vault allocation updated:', updateVaultAllocationSig);
});
commands
.command('deposit')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--amount <number>', 'Token amount to deposit, in decimals')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, amount, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instructions = await kaminoManager.depositToVaultIxs(env.payer.publicKey, kaminoVault, amount);
const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
mode === 'execute' && console.log('User deposit:', depositSig);
});
commands
.command('withdraw')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--amount <number>', 'Shares amount to withdraw')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, amount, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instructions = await kaminoManager.withdrawFromVaultIxs(env.payer.publicKey, kaminoVault, new decimal_js_1.default(amount), await env.connection.getSlot('confirmed'));
const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
mode === 'execute' && console.log('User withdraw:', depositSig);
});
commands
.command('invest-all-reserves')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const instructions = await kaminoManager.investAllReserves(env.payer.publicKey, kaminoVault);
for (let i = 0; i < instructions.length; i++) {
const txInstructions = [];
txInstructions.push(instructions[i]);
const investReserveSig = await processTxn(env.client, env.payer, txInstructions, mode, 2500, [], 400000);
mode === 'execute' && console.log('Reserve invested:', investReserveSig);
}
});
commands
.command('invest-single-reserve')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.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 ({ vault, reserve, mode, staging, multisig }) => {
const env = initializeClient(mode === 'multisig', staging);
const vaultAddress = new web3_js_1.PublicKey(vault);
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const reserveState = await lib_1.Reserve.fetch(env.connection, new web3_js_1.PublicKey(reserve), env.kLendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
const reserveWithAddress = {
address: new web3_js_1.PublicKey(reserve),
state: reserveState,
};
const instructions = await kaminoManager.investSingleReserve(env.payer.publicKey, kaminoVault, reserveWithAddress);
const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 400_000);
mode === 'execute' && console.log('Reserve invested:', investReserveSig);
});
// commands
// .command('close-vault')
// .requiredOption('--vault <string>', 'Vault address')
// .option(`--staging`, 'If true, will use the staging programs')
// .action(async ({vault, staging}) => {
// const env = initializeClient(false, staging);
// const vaultAddress = new PublicKey(vault);
// const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
// const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
// const instructions = await kaminoManager.closeVault(kaminoVault);
// const closeVaultSig = await processTxn(env.client, env.payer, [instructions], 'execute', 2500, []);
// console.log('Vault closed:', closeVaultSig);
// });
commands
.command('get-vault-colls')
.requiredOption('--vault <string>', 'Vault address')
.action(async ({ vault }) => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const vaultAddress = new web3_js_1.PublicKey(vault);
const vaultState = await new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection);
const vaultCollaterals = await kaminoManager.getVaultCollaterals(vaultState, await env.connection.getSlot('confirmed'));
vaultCollaterals.forEach((collateral) => {
console.log('reserve ', collateral.address);
console.log('market overview', collateral.reservesAsCollateral);
console.log('min LTV', collateral.minLTVPct);
console.log('max LTV', collateral.maxLTVPct);
});
});
commands
.command('get-user-shares-for-vault')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--wallet <string>', 'User wailt address')
.action(async ({ vault, wallet }) => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const vaultAddress = new web3_js_1.PublicKey(vault);
const walletAddress = new web3_js_1.PublicKey(wallet);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const userShares = await kaminoManager.getUserSharesBalanceSingleVault(walletAddress, kaminoVault);
console.log(`User shares for vault ${vaultAddress.toBase58()}: ${userShares}`);
});
commands
.command('get-user-shares-all-vaults')
.requiredOption('--wallet <string>', 'User wailt address')
.action(async ({ wallet }) => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const walletAddress = new web3_js_1.PublicKey(wallet);
const userShares = await kaminoManager.getUserSharesBalanceAllVaults(walletAddress);
userShares.forEach((userShares, vaultAddress) => {
console.log(`User shares for vault ${vaultAddress}: ${userShares}`);
});
});
commands
.command('get-tokens-per-share')
.requiredOption('--vault <string>', 'Vault address')
.action(async ({ vault }) => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const vaultAddress = new web3_js_1.PublicKey(vault);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
const tokensPerShare = await kaminoManager.getTokensPerShareSingleVault(kaminoVault, await env.connection.getSlot('confirmed'));
console.log(`Tokens per share for vault ${vaultAddress.toBase58()}: ${tokensPerShare}`);
});
commands
.command('print-vault')
.requiredOption('--vault <string>', 'Vault address')
.action(async ({ vault }) => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const vaultAddress = new web3_js_1.PublicKey(vault);
const kaminoVault = new lib_1.KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
await kaminoVault.getState(env.connection);
const slot = await env.connection.getSlot('confirmed');
const tokensPerShare = await kaminoManager.getTokensPerShareSingleVault(kaminoVault, slot);
const holdings = await kaminoManager.getVaultHoldings(kaminoVault.state, slot);
const vaultState = kaminoVault.state;
const sharesIssued = new decimal_js_1.default(vaultState.sharesIssued.toString()).div(new decimal_js_1.default(vaultState.sharesMintDecimals.toString()));
console.log('Shares issued: ', sharesIssued);
console.log('Holdings: ', holdings);
console.log(`Tokens per share for vault ${vaultAddress.toBase58()}: ${tokensPerShare}`);
});
commands.command('get-oracle-mappings').action(async () => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
console.log('Getting oracle mappings');
const oracleConfigs = await kaminoManager.getScopeOracleConfigs();
console.log('oracleConfigs', JSON.parse(JSON.stringify(oracleConfigs)));
});
commands.command('get-all-vaults').action(async () => {
const env = initializeClient(false, false);
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const allVaults = await kaminoManager.getAllVaults(true);
console.log('all vaults', allVaults);
});
commands
.command('download-lending-market-config')
.requiredOption('--lending-market <string>', 'Lending Market Address')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ lendingMarket, staging }) => {
const env = initializeClient(false, staging);
const lendingMarketAddress = new web3_js_1.PublicKey(lendingMarket);
const lendingMarketState = await lib_1.LendingMarket.fetch(env.connection, lendingMarketAddress, env.kLendProgramId);
if (!lendingMarketState) {
throw new Error('Lending Market not found');
}
fs.mkdirSync('./configs/' + lendingMarketAddress.toBase58(), { recursive: true });
const lendingMarketConfigForFile = lendingMarketState.toJSON();
fs.writeFileSync('./configs/' + lendingMarketAddress.toBase58() + '/market-' + lendingMarketAddress.toBase58() + '.json', JSON.stringify(lendingMarketConfigForFile, null, 2));
});
commands
.command('download-lending-market-config-and-all-reserves-configs')
.requiredOption('--lending-market <string>', 'Lending Market Address')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ lendingMarket, staging }) => {
const env = initializeClient(false, staging);
const decoder = new TextDecoder('utf-8');
const lendingMarketAddress = new web3_js_1.PublicKey(lendingMarket);
const kaminoMarket = await lib_1.KaminoMarket.load(env.connection, lendingMarketAddress, lib_1.DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId);
if (!kaminoMarket) {
throw new Error('Lending Market not found');
}
const lendingMarketState = await lib_1.LendingMarket.fetch(env.connection, lendingMarketAddress, env.kLendProgramId);
if (!lendingMarketState) {
throw new Error('Lending Market not found');
}
fs.mkdirSync('./configs/' + lendingMarketAddress.toBase58(), { recursive: true });
const lendingMarketConfigForFile = lendingMarketState.toJSON();
fs.writeFileSync('./configs/' + lendingMarketAddress.toBase58() + '/market-' + lendingMarketAddress.toBase58() + '.json', JSON.stringify(lendingMarketConfigForFile, null, 2));
kaminoMarket.reserves.forEach(async (reserve) => {
const reserveState = reserve.state;
const reserveName = decoder.decode(Uint8Array.from(reserveState.config.tokenInfo.name)).replace(/\0/g, '');
const reserveConfigDisplay = parseReserveConfigToFile(reserveState.config);
fs.writeFileSync('./configs/' + lendingMarketAddress.toBase58() + '/' + reserveName + '.json', JSON.stringify(reserveConfigDisplay, null, 2));
});
});
commands
.command('update-lending-market-from-config')
.requiredOption('--lending-market <string>', 'Lending Market address')
.requiredOption('--lending-market-config-path <string>', 'Path for the market config')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ lendingMarket, lendingMarketConfigPath, mode, staging }) => {
const env = initializeClient(mode === 'multisig', staging);
const lendingMarketAddress = new web3_js_1.PublicKey(lendingMarket);
const lendingMarketState = await lib_1.LendingMarket.fetch(env.connection, lendingMarketAddress, env.kLendProgramId);
if (!lendingMarketState) {
throw new Error('Lending Market not found');
}
const marketWithAddress = {
address: lendingMarketAddress,
state: lendingMarketState,
};
if (mode === 'multisig') {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const newLendingMarket = lib_1.LendingMarket.fromJSON(JSON.parse(fs.readFileSync(lendingMarketConfigPath, 'utf8')));
const ixns = kaminoManager.updateLendingMarketIxs(marketWithAddress, newLendingMarket);
// executing 6 ixns in a txn to make sure they fit
for (let ixnIndex = 0; ixnIndex < ixns.length; ixnIndex += 6) {
const ixnToExecute = ixns.slice(ixnIndex, ixnIndex + 6);
const _updateLendingMarketSig = await processTxn(env.client, env.payer, ixnToExecute, mode, 2500, [], 400_000);
}
mode === 'execute' &&
console.log('Reserve Updated with new config -> ', JSON.parse(JSON.stringify(newLendingMarket)));
});
commands
.command('update-lending-market-owner')
.requiredOption('--lending-market <string>', 'Lending Market address')
.requiredOption(`--mode <string>`, 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ lendingMarket, mode, staging }) => {
const env = initializeClient(mode === 'multisig', staging);
const lendingMarketAddress = new web3_js_1.PublicKey(lendingMarket);
const lendingMarketState = await lib_1.LendingMarket.fetch(env.connection, lendingMarketAddress, env.kLendProgramId);
if (!lendingMarketState) {
throw new Error('Lending Market not found');
}
const marketWithAddress = {
address: lendingMarketAddress,
state: lendingMarketState,
};
if (!lendingMarketState) {
throw new Error('Lending Market not found');
}
if (mode === 'multisig') {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new lib_1.KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
const ixn = kaminoManager.updateLendingMarketOwnerIxs(marketWithAddress);
const _updateLendingMarketSig = await processTxn(env.client, env.payer, [ixn], mode, 2500, [], 400_000);
mode === 'execute' &&
console.log('Lending market admin updated to the new admin -> ', JSON.parse(JSON.stringify(lendingMarketState.lendingMarketOwnerCached)));
});
await commands.parseAsync();
}
main()
.then(() => {
process.exit();
})
.catch((e) => {
console.error('\n\nKamino CLI exited with error:\n\n', e);
process.exit(1);
});
function parseKeypairFile(file) {
return web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(require('fs').readFileSync(file))));
}
function initializeClient(multisig, staging) {
const admin = process.env.ADMIN;
const rpc = process.env.RPC;
const kLendProgramId = staging ? process.env.KLEND_PROGRAM_ID_STAGING : process.env.KLEND_PROGRAM_ID_MAINNET;
const kVaultProgramId = staging ? process.env.KVAULT_PROGRAM_ID_STAGING : process.env.KVAULT_PROGRAM_ID_MAINNET;
// Get connection first
const env = setUpProgram({
adminFilePath: admin,
rpc: rpc,
kLendProgramId: new web3_js_1.PublicKey(kLendProgramId),
kVaultProgramId: new web3_js_1.PublicKey(kVaultProgramId),
});
!multisig && console.log('\nSettings ⚙️');
!multisig && console.log('Admin:', admin);
!multisig && console.log('Rpc:', rpc);
!multisig && console.log('kLendProgramId:', kLendProgramId);
!multisig && console.log('kVaultProgramId:', kVaultProgramId);
return env;
}
function setUpProgram(args) {
const chain = {
name: 'mainnet-beta',
endpoint: args.rpc,
wsEndpoint: args.rpc,
chainID: lib_1.MAINNET_BETA_CHAIN_ID,
displayName: 'Mainnet Beta (Triton)',
};
const client = new lib_1.Web3Client(chain);
const connection = client.sendConnection;
const payer = parseKeypairFile(args.adminFilePath);
const wallet = new anchor.Wallet(payer);
const provider = new anchor.AnchorProvider(connection, wallet, anchor.AnchorProvider.defaultOptions());
return {
provider,
payer,
client,
connection,
kLendProgramId: args.kLendProgramId,
kVaultProgramId: args.kVaultProgramId,
};
}
async function processTxn(web3Client, admin, ixns, mode, priorityFeeMultiplier = 2500, extraSigners, computeUnits = 200_000, priorityFeeLamports = 1000) {
if (mode !== 'inspect' && mode !== 'simulate' && mode !== 'execute' && mode !== 'multisig') {
throw new Error('Invalid mode: ' + mode + '. Must be one of: inspect/simulate/execute/multisig');
}
if (mode === 'multisig') {
const { blockhash } = await web3Client.connection.getLatestBlockhash();
const txn = new web3_js_1.Transaction();
txn.add(...ixns);
txn.recentBlockhash = blockhash;
txn.feePayer = admin.publicKey;
console.log((0, base58_js_1.binary_to_base58)(txn.serializeMessage()));
return '';
}
else {
const microLamport = priorityFeeLamports * 10 ** 6; // 1000 lamports
const microLamportsPrioritizationFee = microLamport / computeUnits;
const tx = new web3_js_1.Transaction();
const { blockhash } = await web3Client.connection.getLatestBlockhash();
if (priorityFeeMultiplier) {
const priorityFeeIxn = createAddExtraComputeUnitFeeTransaction(computeUnits, microLamportsPrioritizationFee * priorityFeeMultiplier);
tx.add(...priorityFeeIxn);
}
tx.recentBlockhash = blockhash;
tx.feePayer = admin.publicKey;
tx.add(...ixns);
if (mode === 'execute') {
return await (0, lib_1.signSendAndConfirmRawTransactionWithRetry)({
mainConnection: web3Client.sendConnection,
extraConnections: [],
tx: new web3_js_1.VersionedTransaction(tx.compileMessage()),
signers: [admin, ...extraSigners],
commitment: 'confirmed',
sendTransactionOptions: {
skipPreflight: true,
preflightCommitment: 'confirmed',
},
});
}
else if (mode === 'simulate') {
const simulation = await web3Client.sendConnection.simulateTransaction(new web3_js_1.VersionedTransaction(tx.compileMessage()));
if (simulation.value.logs && simulation.value.logs.length > 0) {
console.log('Simulation: \n' + simulation.value.logs);
}
else {
console.log('Simulation failed: \n' + simulation);
}
}
else if (mode === 'inspect') {
console.log('Tx in B64', `https://explorer.solana.com/tx/inspector?message=${encodeURIComponent(tx.serializeMessage().toString('base64'))}`);
}
return '';
}
}
function createAddExtraComputeUnitFeeTransaction(units, microLamports) {
const ixns = [];
ixns.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units }));
ixns.push(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports }));
return ixns;
}
function parseReserveConfigFromFile(farmConfigFromFile) {
const reserveConfigFields = {
status: farmConfigFromFile.status,
loanToValuePct: farmConfigFromFile.loanToValuePct,
liquidationThresholdPct: farmConfigFromFile.liquidationThresholdPct,
minLiquidationBonusBps: farmConfigFromFile.minLiquidationBonusBps,
protocolLiquidationFeePct: farmConfigFromFile.protocolLiquidationFeePct,
protocolTakeRatePct: farmConfigFromFile.protocolLiquidationFeePct,
assetTier: farmConfigFromFile.assetTier,
maxLiquidationBonusBps: farmConfigFromFile.maxLiquidationBonusBps,
badDebtLiquidationBonusBps: farmConfigFromFile.badDebtLiquidationBonusBps,
fees: {
borrowFeeSf: fraction_1.Fraction.fromDecimal(new decimal_js_1.default(farmConfigFromFile.fees.borrowFee)).valueSf,
flashLoanFeeSf: fraction_1.Fraction.fromDecimal(new decimal_js_1.default(farmConfigFromFile.fees.flashLoanFee)).valueSf,
padding: Array(8).fill(0),
},
depositLimit: new anchor_1.BN(farmConfigFromFile.depositLimit),
borrowLimit: new anchor_1.BN(farmConfigFromFile.borrowLimit),
tokenInfo: {
name: (0, lib_1.encodeTokenName)(farmConfigFromFile.tokenInfo.name),
heuristic: new types_1.PriceHeuristic({
lower: new anchor_1.BN(farmConfigFromFile.tokenInfo.heuristic.lower),
upper: new anchor_1.BN(farmConfigFromFile.tokenInfo.heuristic.upper),
exp: new anchor_1.BN(farmConfigFromFile.tokenInfo.heuristic.exp),
}),
maxTwapDivergenceBps: new anchor_1.BN(farmConfigFromFile.tokenInfo.maxTwapDivergenceBps),
maxAgePriceSeconds: new anchor_1.BN(farmConfigFromFile.tokenInfo.maxAgePriceSeconds),
maxAgeTwapSeconds: new anchor_1.BN(farmConfigFromFile.tokenInfo.maxAgeTwapSeconds),
...parseOracleConfiguration(farmConfigFromFile),
blockPriceUsage: farmConfigFromFile.tokenInfo.blockPriceUsage,
reserved: Array(7).fill(0),
padding: Array(19).fill(new anchor_1.BN(0)),
},
borrowRateCurve: parseBorrowRateCurve(farmConfigFromFile),
depositWithdrawalCap: new types_1.WithdrawalCaps({
configCapacity: new anchor_1.BN(farmConfigFromFile.depositWithdrawalCap.configCapacity),
currentTotal: new anchor_1.BN(0),
lastIntervalStartTimestamp: new anchor_1.BN(0),
configIntervalLengthSeconds: new anchor_1.BN(farmConfigFromFile.depositWithdrawalCap.configIntervalLengthSeconds),
}),
debtWithdrawalCap: new types_1.WithdrawalCaps({
configCapacity: new anchor_1.BN(farmConfigFromFile.debtWithdrawalCap.configCapacity),
currentTotal: new anchor_1.BN(0),
lastIntervalStartTimestamp: new anchor_1.BN(0),
configIntervalLengthSeconds: new anchor_1.BN(farmConfigFromFile.debtWithdrawalCap.configIntervalLengthSeconds),
}),
deleveragingMarginCallPeriodSecs: new anchor_1.BN(farmConfigFromFile.deleveragingMarginCallPeriodSecs),
borrowFactorPct: new anchor_1.BN(farmConfigFromFile.borrowFactorPct),
elevationGroups: farmConfigFromFile.elevationGroups,
deleveragingThresholdSlotsPerBps: new anchor_1.BN(farmConfigFromFile.deleveragingThresholdSlotsPerBps),
disableUsageAsCollOutsideEmode: farmConfigFromFile.disableUsageAsCollOutsideEmode,
utilizationLimitBlockBorrowingAbove: farmConfigFromFile.utilizationLimitBlockBorrowingAbove,
hostFixedInterestRateBps: farmConfigFromFile.hostFixedInterestRateBps,
borrowLimitOutsideElevationGroup: new anchor_1.BN(farmConfigFromFile.borrowLimitOutsideElevationGroup),
borrowLimitAgainstThisCollateralInElevationGroup: parseReserveBorrowLimitAgainstCollInEmode(farmConfigFromFile),
reserved1: Array(2).fill(0),
reserved2: Array(2).fill(0),
reserved3: Array(8).fill(0),
};
return new types_1.ReserveConfig(reserveConfigFields);
}
function parseOracleConfiguration(farmConfigFromFile) {
const pythConfiguration = new types_2.PythConfiguration({
price: new web3_js_1.PublicKey(farmConfigFromFile.tokenInfo.pythConfiguration.price),
});
const switchboardConfiguration = new types_2.SwitchboardConfiguration({
priceAggregator: new web3_js_1.PublicKey(farmConfigFromFile.tokenInfo.switchboardConfiguration.priceAggregator),
twapAggregator: new web3_js_1.PublicKey(farmConfigFromFile.tokenInfo.switchboardConfiguration.twapAggregator),
});
const priceChain = [65535, 65535, 65535, 655