UNPKG

@kamino-finance/klend-sdk

Version:

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

1,235 lines (1,046 loc) 77.3 kB
import dotenv from 'dotenv'; import { Command } from 'commander'; import { ComputeBudgetProgram, Connection, Keypair, PublicKey, Signer, Transaction, TransactionInstruction, TransactionMessage, TransactionSignature, VersionedTransaction, } from '@solana/web3.js'; import { AssetReserveConfigCli, Chain, createLookupTableIx, DEFAULT_RECENT_SLOT_DURATION_MS, encodeTokenName, extendLookupTableIxs, getLookupTableAccounts, getMedianSlotDurationInMsFromLastEpochs, globalConfigPda, initLookupTableIx, KaminoManager, KaminoMarket, KaminoVault, KaminoVaultConfig, lamportsToDecimal, LendingMarket, MAINNET_BETA_CHAIN_ID, parseZeroPaddedUtf8, printHoldings, PROGRAM_ID, Reserve, ReserveAllocationConfig, ReserveWithAddress, signSendAndConfirmRawTransactionWithRetry, sleep, Web3Client, } from './lib'; import * as anchor from '@coral-xyz/anchor'; import { binary_to_base58 } from 'base58-js'; import { BorrowRateCurve, CurvePointFields, PriceHeuristic, ReserveConfig, ReserveConfigFields, ScopeConfiguration, TokenInfo, WithdrawalCaps, } from './idl_codegen/types'; import { Fraction } from './classes/fraction'; import Decimal from 'decimal.js'; import { BN } from '@coral-xyz/anchor'; import { PythConfiguration, SwitchboardConfiguration } from './idl_codegen_kamino_vault/types'; import * as fs from 'fs'; import { MarketWithAddress } from './utils/managerTypes'; import { ManagementFeeBps, PendingVaultAdmin, PerformanceFeeBps, } from './idl_codegen_kamino_vault/types/VaultConfigField'; import { getAccountOwner } from './utils/rpc'; import { getAssociatedTokenAddressSync } from '@solana/spl-token'; dotenv.config({ path: `.env${process.env.ENV ? '.' + process.env.ENV : ''}`, }); async function main() { const commands = new 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 PublicKey(multisig) : PublicKey.default; const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const { market: marketKp, ixs: createMarketIxs } = await kaminoManager.createMarketIxs({ admin: mode === 'multisig' ? multisigPk : env.payer.publicKey, }); await processTxn(env.client, env.payer, createMarketIxs, 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 PublicKey(mint); const tokenMintProgramId = new PublicKey(mintProgramId); const marketAddress = new PublicKey(market); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const multisigPk = multisig ? new PublicKey(multisig) : PublicKey.default; const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8')); const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile); const assetConfig = new AssetReserveConfigCli(tokenMint, tokenMintProgramId, reserveConfig); const adminAta = mode === 'multisig' ? getAssociatedTokenAddressSync(tokenMint, multisigPk) : getAssociatedTokenAddressSync(tokenMint, env.payer.publicKey); const { reserve, txnIxs } = await kaminoManager.addAssetToMarketIxs({ admin: mode === 'multisig' ? multisigPk : env.payer.publicKey, adminLiquiditySource: adminAta, marketAddress: marketAddress, assetConfig: assetConfig, }); console.log('reserve: ', reserve.publicKey); const _createReserveSig = await processTxn(env.client, env.payer, txnIxs[0], mode, 2500, [reserve]); const [lut, createLutIxs] = await createUpdateReserveConfigLutIxs(env, marketAddress, reserve.publicKey); await processTxn(env.client, env.payer, createLutIxs, mode, 2500, []); const _updateSig = await processTxn(env.client, env.payer, txnIxs[1], mode, 2500, [], 400_000, 1000, [lut]); 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 PublicKey(reserve); const reserveState = await Reserve.fetch(env.connection, reserveAddress, env.kLendProgramId); if (!reserveState) { throw new Error('Reserve not found'); } const marketAddress = reserveState.lendingMarket; const marketState = await LendingMarket.fetch(env.connection, marketAddress, env.kLendProgramId); if (!marketState) { throw new Error('Market not found'); } const marketWithAddress: MarketWithAddress = { address: marketAddress, state: marketState, }; if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const reserveConfigFromFile = JSON.parse(fs.readFileSync(reserveConfigPath, 'utf8')); const reserveConfig = parseReserveConfigFromFile(reserveConfigFromFile); const ixs = await kaminoManager.updateReserveIxs( marketWithAddress, reserveAddress, reserveConfig, reserveState, updateEntireConfig ); const _updateReserveSig = await processTxn(env.client, env.payer, ixs, 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 PublicKey(reserve); const reserveState = await 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' ) .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(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored') .action(async ({ mint, mode, name, tokenName, extraTokenName, staging, multisig }) => { const env = initializeClient(mode === 'multisig', staging); const tokenMint = new PublicKey(mint); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const multisigPk = multisig ? new PublicKey(multisig) : PublicKey.default; const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const tokenProgramID = await getAccountOwner(env.connection, tokenMint); const kaminoVaultConfig = new KaminoVaultConfig({ admin: mode === 'multisig' ? multisigPk : env.payer.publicKey, tokenMint: tokenMint, tokenMintProgramId: tokenProgramID, performanceFeeRatePercentage: new Decimal(0.0), managementFeeRatePercentage: new Decimal(0.0), name, vaultTokenSymbol: tokenName, vaultTokenName: extraTokenName, }); const { vault: vaultKp, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig); const _createVaultSig = await processTxn( env.client, env.payer, [...instructions.createAtaIfNeededIxs, ...instructions.initVaultIxs, instructions.createLUTIx], mode, 2500, [vaultKp] ); await sleep(2000); const _populateLUTSig = await processTxn( env.client, env.payer, [...instructions.populateLUTIxs, ...instructions.cleanupIxs], mode, 2500, [] ); const _setSharesMetadataSig = await processTxn( env.client, env.payer, [instructions.initSharesMetadataIx], mode, 2500, [] ); mode === 'execute' && console.log('Vault created:', vaultKp.publicKey.toBase58()); }); commands .command('set-shares-metadata') .requiredOption('--vault <string>', 'Vault address') .requiredOption('--symbol <string>', 'The symbol of the kVault token') .requiredOption('--extraName <string>', 'The name of the kVault token, appended to the symbol') .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' ) .action(async ({ vault, symbol, extraName, mode }) => { const env = initializeClient(false, false); const kVault = new KaminoVault(new PublicKey(vault)); const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const ix = await kaminoManager.getSetSharesMetadataIx(kVault, symbol, extraName); await processTxn(env.client, env.payer, [ix], mode, 2500, []); }); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new PendingVaultAdmin(), newAdmin); const updateVaultPendingAdminSig = await processTxn( env.client, env.payer, [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs], mode, 2500, [] ); mode === 'execute' && console.log('Pending admin updated:', updateVaultPendingAdminSig); }); 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 - 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, field, value, mode, staging, multisig }) => { const env = initializeClient(mode === 'multisig', staging); const vaultAddress = new PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, field, value); const updateVaultPendingAdminSig = await processTxn( env.client, env.payer, [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs], mode, 2500, [] ); mode === 'execute' && console.log('Vault 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new ManagementFeeBps(), feeBps); const updateVaultConfigSig = await processTxn( env.client, env.payer, [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs], mode, 2500, [] ); mode === 'execute' && console.log('Management fee updated:', updateVaultConfigSig); }); 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 - 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 ({ lut, addresses, mode, staging, multisig }) => { const env = initializeClient(mode === 'multisig', staging); const lutAddress = new PublicKey(lut); const addressesArr = addresses.split(' ').map((address: string) => new PublicKey(address)); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const instructions = await kaminoManager.insertIntoLUTIxs(env.payer.publicKey, lutAddress, addressesArr); const updateVaultConfigSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []); mode === 'execute' && console.log('Management fee updated:', updateVaultConfigSig); }); commands.command('create-lut').action(async () => { const env = initializeClient(false, false); const initLutIx = initLookupTableIx(env.payer.publicKey, await env.connection.getSlot()); const updateVaultConfigSig = await processTxn(env.client, env.payer, [initLutIx[0]], 'execute', 2500, []); console.log(`LUT created: ${initLutIx[1].toString()} tx id: ${updateVaultConfigSig}`); }); commands .command('sync-vault-lut') .requiredOption('--vault <string>', 'The vault address to sync') .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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const syncLUTIxs = await kaminoManager.syncVaultLUTIxs(kaminoVault); // if we need to create the LUT we have to do that in a separate tx and wait a little bit after if (syncLUTIxs.setupLUTIfNeededIxs.length > 0) { const setupLUTSig = await processTxn(env.client, env.payer, syncLUTIxs.setupLUTIfNeededIxs, mode, 2500, []); await sleep(2000); mode === 'execute' && console.log('LUT created and set to the vault:', setupLUTSig); } // if there are accounts to be added to the LUT we have to do that in a separate tx for (const ix of syncLUTIxs.syncLUTIxs) { const insertIntoLUTSig = await processTxn(env.client, env.payer, [ix], mode, 2500, []); mode === 'execute' && console.log('Accounts added to the LUT:', insertIntoLUTSig); } }); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new PerformanceFeeBps(), feeBps); const updateVaultPerfFeeSig = await processTxn( env.client, env.payer, [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs], 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.acceptVaultOwnershipIxs(kaminoVault); const acceptVaultOwnershipSig = await processTxn( env.client, env.payer, [instructions.acceptVaultOwnershipIx], mode, 2500, [] ); mode === 'execute' && console.log('Vault ownership accepted:', acceptVaultOwnershipSig); // send the LUT mgmt ixs one by one const lutIxs = [...instructions.updateLUTIxs]; for (let i = 0; i < lutIxs.length; i++) { const lutIxsGroup = lutIxs.slice(i, i + 1); const lutIxsSig = await processTxn(env.client, env.payer, lutIxsGroup, mode, 2500, []); mode === 'execute' && console.log('LUT updated:', lutIxsSig); } }); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instruction = await kaminoManager.giveUpPendingFeesIx(kaminoVault, new Decimal(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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new 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('remove-vault-allocation') .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 reserveAddress = new PublicKey(reserve); const vaultAddress = new PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instruction = await kaminoManager.removeReserveFromAllocationIx(kaminoVault, reserveAddress); if (instruction) { const updateVaultAllocationSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []); mode === 'execute' && console.log('Vault allocation updated:', updateVaultAllocationSig); } }); commands .command('stake') .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 PublicKey(vault); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const stakeIxs = await new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ).stakeSharesIxs(env.payer.publicKey, kaminoVault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const withdrawPendingFeesSig = await processTxn(env.client, env.payer, stakeIxs, mode, 2500, []); mode === 'execute' && console.log('Stake into vault farm:', 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 PublicKey(reserve); const vaultAddress = new PublicKey(vault); const allocationWeightValue = Number(allocationWeight); const allocationCapDecimal = new Decimal(allocationCap); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const reserveState = await Reserve.fetch(env.connection, reserveAddress, env.kLendProgramId); if (!reserveState) { throw new Error('Reserve not found'); } const reserveWithAddress: ReserveWithAddress = { address: reserveAddress, state: reserveState, }; const firstReserveAllocationConfig = new ReserveAllocationConfig( reserveWithAddress, allocationWeightValue, allocationCapDecimal ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.updateVaultReserveAllocationIxs( kaminoVault, firstReserveAllocationConfig ); const updateVaultAllocationSig = await processTxn( env.client, env.payer, [instructions.updateReserveAllocationIx, ...instructions.updateLUTIxs], 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const depositInstructions = await kaminoManager.depositToVaultIxs(env.payer.publicKey, kaminoVault, amount); const instructions = [...depositInstructions.depositIxs, ...depositInstructions.stakeInFarmIfNeededIxs]; const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const withdrawIxs = await kaminoManager.withdrawFromVaultIxs( env.payer.publicKey, kaminoVault, new Decimal(amount), await env.connection.getSlot('confirmed') ); const withdrawSig = await processTxn( env.client, env.payer, [...withdrawIxs.unstakeFromFarmIfNeededIxs, ...withdrawIxs.withdrawIxs, ...withdrawIxs.postWithdrawIxs], mode, 2500, [], 800_000 ); mode === 'execute' && console.log('User withdraw:', withdrawSig); }); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const instructions = await kaminoManager.investAllReservesIxs(env.payer.publicKey, kaminoVault); for (let i = 0; i < instructions.length; i++) { const txInstructions: TransactionInstruction[] = []; txInstructions.push(instructions[i]); const investReserveSig = await processTxn(env.client, env.payer, txInstructions, mode, 2500, [], 800000); 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 PublicKey(vault); if (mode === 'multisig' && !multisig) { throw new Error('If using multisig mode, multisig is required'); } const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const reserveState = await Reserve.fetch(env.connection, new PublicKey(reserve), env.kLendProgramId); if (!reserveState) { throw new Error('Reserve not found'); } const reserveWithAddress: ReserveWithAddress = { address: new PublicKey(reserve), state: reserveState, }; const instructions = await kaminoManager.investSingleReserveIxs( env.payer.publicKey, kaminoVault, reserveWithAddress ); const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_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 slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const vaultState = await new 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-vault-overview') .requiredOption('--vault <string>', 'Vault address') .action(async ({ vault }) => { const env = initializeClient(false, false); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection); const vaultOverview = await kaminoManager.getVaultOverview( vaultState, new Decimal(1.0), await env.connection.getSlot('confirmed') ); console.log('vaultOverview', vaultOverview); }); commands .command('get-vault-allocation-distribution') .requiredOption('--vault <string>', 'Vault address') .action(async ({ vault }) => { const env = initializeClient(false, false); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection); const allocationDistribution = kaminoManager.getAllocationsDistribuionPct(vaultState); allocationDistribution.forEach((allocation, reserveAddress) => { console.log('reserve ', reserveAddress); console.log('allocation', allocation); }); }); 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 slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const walletAddress = new PublicKey(wallet); const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId); const userShares = await kaminoManager.getUserSharesBalanceSingleVault(walletAddress, kaminoVault); console.log( `User shares for vault ${vaultAddress.toBase58()}: unstaked shares: ${ userShares.unstakedShares } staked shares: ${userShares.stakedShares} total shares: ${userShares.totalShares}` ); }); commands .command('get-user-shares-all-vaults') .requiredOption('--wallet <string>', 'User wailt address') .action(async ({ wallet }) => { const env = initializeClient(false, false); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const walletAddress = new PublicKey(wallet); const userShares = await kaminoManager.getUserSharesBalanceAllVaults(walletAddress); userShares.forEach((userShares, vaultAddress) => { console.log( `User shares for vault ${vaultAddress}: staked shares ${userShares.stakedShares} unstaked shares ${userShares.unstakedShares} total shares ${userShares.totalShares}` ); }); }); commands .command('get-tokens-per-share') .requiredOption('--vault <string>', 'Vault address') .action(async ({ vault }) => { const env = initializeClient(false, false); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const kaminoVault = new 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 slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultAddress = new PublicKey(vault); const kaminoVault = new 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 = lamportsToDecimal( vaultState.sharesIssued.toString(), vaultState.sharesMintDecimals.toString() ); const vaultOverview = await kaminoManager.getVaultOverview(vaultState, new Decimal(1.0), slot); console.log('farm', vaultState.vaultFarm.toString()); console.log('vault token mint', vaultState.tokenMint.toBase58()); console.log('Name: ', kaminoManager.getDecodedVaultName(kaminoVault.state!)); console.log('Shares issued: ', sharesIssued); printHoldings(holdings); console.log(`Tokens per share for vault ${vaultAddress.toBase58()}: ${tokensPerShare}`); console.log('vaultOverview', vaultOverview); for (const [reserveAddress, reserveOverview] of vaultOverview.reservesOverview) { console.log(`reserve ${reserveAddress.toBase58()} supplyAPY ${reserveOverview.supplyAPY}`); } }); commands.command('get-oracle-mappings').action(async () => { const env = initializeClient(false, false); const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, 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 slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const allVaults = await kaminoManager.getAllVaults(); console.log('all vaults', allVaults); }); commands .command('get-all-vaults-for-token') .requiredOption('--token <string>', 'Token address') .action(async ({ token }) => { const env = initializeClient(false, false); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const allVaults = await kaminoManager.getAllVaultsForToken(new PublicKey(token)); console.log('all vaults for token ', token, allVaults); }); commands.command('get-all-vaults-pks').action(async () => { const env = initializeClient(false, false); const kaminoManager = new KaminoManager( env.connection, DEFAULT_RECENT_SLOT_DURATION_MS, env.kLendProgramId, env.kVaultProgramId ); const allVaults = await kaminoManager.getAllVaults(); console.log( 'all vaults', allVaults.map((vault) => vault.address.toBase58()) ); }); commands .command('get-simulated-interest-and-fees') .requiredOption('--vault <string>', 'Vault address') .action(async ({ vault }) => { const env = initializeClient(false, false); const vaultAddress = new PublicKey(vault); const slotDuration = await getMedianSlotDurationInMsFromLastEpochs(); const kaminoManager = new KaminoManager(env.connection, slotDuration, env.kLendProgramId, env.kVaultProgramId); const vaultState = await new KaminoVault(vaultAdd