@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
1,235 lines (1,046 loc) • 77.3 kB
text/typescript
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