@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
1,420 lines (1,271 loc) • 87.3 kB
text/typescript
import dotenv from 'dotenv';
import { Command } from 'commander';
import { Address, address, Instruction } from '@solana/kit';
import {
AssetReserveConfigCli,
calculateAPYFromAPR,
createLookupTableIx,
DEFAULT_PUBLIC_KEY,
DEFAULT_RECENT_SLOT_DURATION_MS,
encodeTokenName,
extendLookupTableIxs,
getMedianSlotDurationInMsFromLastEpochs,
globalConfigPda,
initLookupTableIx,
KaminoManager,
KaminoMarket,
KaminoReserve,
KaminoVault,
KaminoVaultConfig,
lamportsToDecimal,
LendingMarket,
parseZeroPaddedUtf8,
Reserve,
ReserveAllocationConfig,
ReserveWithAddress,
sleep,
} from '../lib';
import {
BorrowRateCurve,
CurvePointFields,
PriceHeuristic,
ReserveConfig,
ReserveConfigFields,
ScopeConfiguration,
TokenInfo,
WithdrawalCaps,
} from '../@codegen/klend/types';
import { Fraction } from '../classes/fraction';
import Decimal from 'decimal.js';
import BN from 'bn.js';
import { PythConfiguration, SwitchboardConfiguration } from '../@codegen/kvault/types';
import * as fs from 'fs';
import { MarketWithAddress } from '../utils/managerTypes';
import { ManagementFeeBps, PendingVaultAdmin, PerformanceFeeBps } from '../@codegen/kvault/types/VaultConfigField';
import { getAccountOwner } from '../utils/rpc';
import { fetchMint, findAssociatedTokenPda } from '@solana-program/token-2022';
import { initEnv, ManagerEnv } from './tx/ManagerEnv';
import { processTx } from './tx/processor';
import { getPriorityFeeAndCuIxs } from '../client/tx/priorityFee';
import { fetchAddressLookupTable, fetchAllAddressLookupTable } from '@solana-program/address-lookup-table';
import { noopSigner, parseKeypairFile } from '../utils/signer';
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|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ mode, staging, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig pubkey is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(ms, staging);
const admin = await env.getSigner();
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const { market: marketKp, ixs: createMarketIxs } = await kaminoManager.createMarketIxs({
admin,
});
await processTx(
env.c,
admin,
[
...createMarketIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Market created:', marketKp.address);
});
commands
.command('add-asset-to-market')
.requiredOption('--market <string>', 'Market address to add asset to')
.requiredOption('--mint <string>', 'Reserve liquidity token mint')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ market, mint, reserveConfigPath, mode, staging }) => {
const env = await initEnv(undefined, staging);
const tokenMint = address(mint);
const marketAddress = address(market);
const existingMarket = await KaminoMarket.load(
env.c.rpc,
marketAddress,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
false
);
if (existingMarket === null) {
throw new Error(`Market ${marketAddress} does not exist`);
}
const signer = await env.getSigner({ market: existingMarket });
const mintAccount = await fetchMint(env.c.rpc, mint);
const tokenMintProgramId = mintAccount.programAddress;
const kaminoManager = new KaminoManager(
env.c.rpc,
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] = await findAssociatedTokenPda({
mint: tokenMint,
owner: signer.address,
tokenProgram: tokenMintProgramId,
});
const { reserve, txnIxs } = await kaminoManager.addAssetToMarketIxs({
admin: signer,
adminLiquiditySource: adminAta,
marketAddress: marketAddress,
assetConfig: assetConfig,
});
console.log('reserve: ', reserve.address);
const _createReserveSig = await processTx(
env.c,
signer,
[
...txnIxs[0],
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
const [lut, createLutIxs] = await createUpdateReserveConfigLutIxs(env, marketAddress, reserve.address);
await processTx(
env.c,
signer,
[
...createLutIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode
);
const lutAcc = await fetchAddressLookupTable(env.c.rpc, lut);
const _updateReserveSig = await processTx(
env.c,
signer,
[
...txnIxs[1],
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 400_000,
}),
],
mode,
[lutAcc]
);
mode === 'execute' &&
console.log(
'Reserve Created with config:',
JSON.parse(JSON.stringify(reserveConfig)),
'\nreserve address:',
reserve.address
);
});
commands
.command('update-reserve-config')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption('--reserve-config-path <string>', 'Path for the reserve config')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option('--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 }) => {
const env = await initEnv(undefined, staging);
const reserveAddress = address(reserve);
const reserveState = await Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (reserveState === null) {
throw new Error(`Reserve ${reserveAddress} not found`);
}
const marketAddress = reserveState.lendingMarket;
const marketState = await KaminoMarket.load(
env.c.rpc,
marketAddress,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
false
);
if (marketState === null) {
throw new Error(`Market ${marketAddress} not found`);
}
const signer = await env.getSigner({ market: marketState });
const marketWithAddress: MarketWithAddress = {
address: marketAddress,
state: marketState.state,
};
const kaminoManager = new KaminoManager(
env.c.rpc,
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(
signer,
marketWithAddress,
reserveAddress,
reserveConfig,
reserveState,
updateEntireConfig
);
await processTx(
env.c,
signer,
[
...ixs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 400_000,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Reserve Updated with config -> ', JSON.parse(JSON.stringify(reserveConfig)));
});
commands
.command('download-reserve-config')
.requiredOption('--reserve <string>', 'Reserve address')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ reserve, staging }) => {
const env = await initEnv(undefined, staging);
const reserveAddress = address(reserve);
const reserveState = await Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
fs.mkdirSync('./configs/' + reserveState.lendingMarket, { recursive: true });
const decoder = new TextDecoder('utf-8');
const reserveName = decoder.decode(Uint8Array.from(reserveState.config.tokenInfo.name)).replace(/\0/g, '');
const reserveConfigDisplay = parseReserveConfigToFile(reserveState.config);
fs.writeFileSync(
'./configs/' + reserveState.lendingMarket + '/' + reserveName + '.json',
JSON.stringify(reserveConfigDisplay, null, 2)
);
});
commands
.command('create-vault')
.requiredOption('--mint <string>', 'Vault token mint')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.requiredOption('--name <string>', 'The onchain name of the strat')
.requiredOption('--tokenName <string>', 'The name of the token in the vault')
.requiredOption('--extraTokenName <string>', 'The extra string appended to the token symbol')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ mint, mode, name, tokenName, extraTokenName, staging, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(staging, ms);
const tokenMint = address(mint);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const admin = await env.getSigner();
const tokenProgramID = await getAccountOwner(env.c.rpc, tokenMint);
const kaminoVaultConfig = new KaminoVaultConfig({
admin,
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);
await processTx(
env.c,
admin,
[
...instructions.createAtaIfNeededIxs,
...instructions.initVaultIxs,
instructions.createLUTIx,
instructions.setFarmToVaultIx,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
await sleep(2000);
// create the farm
await processTx(
env.c,
admin,
[
...instructions.createVaultFarm.setupFarmIxs,
...instructions.createVaultFarm.updateFarmIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
await sleep(2000);
await processTx(
env.c,
admin,
[
...instructions.populateLUTIxs,
...instructions.cleanupIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
await processTx(
env.c,
admin,
[
instructions.initSharesMetadataIx,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Vault created:', vaultKp.address);
});
commands
.command('set-shares-metadata')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.requiredOption('--symbol <string>', 'The symbol of the kVault token')
.requiredOption('--extraName <string>', 'The name of the kVault token, appended to the symbol')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, mode, symbol, extraName, staging }) => {
const env = await initEnv(undefined, staging);
const kVault = new KaminoVault(env.c.rpc, address(vault));
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const vaultState = await kVault.getState();
const signer = await env.getSigner({ vaultState });
const ix = await kaminoManager.getSetSharesMetadataIx(signer, kVault, symbol, extraName);
await processTx(
env.c,
signer,
[
ix,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
});
commands
.command('update-vault-pending-admin')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--new-admin <string>', 'Pubkey of the new admin')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, newAdmin, mode, staging }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.updateVaultConfigIxs(
kaminoVault,
new PendingVaultAdmin(),
newAdmin,
signer,
undefined,
true
);
await processTx(
env.c,
signer,
[
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log(`Pending admin updated to ${newAdmin}`);
});
commands
.command('update-vault-config')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--field <string>', 'The field to update')
.requiredOption('--value <string>', 'The value to update the field to')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--skip-lut-update`, 'If set, it will skip the LUT update')
.option(
`--lutSigner <string>`,
'If set, it will use the provided signer instead of the default one for the LUT update'
)
.action(async ({ vault, field, value, mode, staging, skipLutUpdate, lutSigner }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
let lutSignerOrUndefined = undefined;
if (lutSigner) {
lutSignerOrUndefined = await parseKeypairFile(lutSigner as string);
}
const shouldSkipLutUpdate = !!skipLutUpdate;
const instructions = await kaminoManager.updateVaultConfigIxs(
kaminoVault,
field,
value,
signer,
lutSignerOrUndefined,
shouldSkipLutUpdate
);
await processTx(
env.c,
signer,
[
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Vault updated');
});
commands
.command('update-vault-mgmt-fee')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--fee-bps <string>', 'Pubkey of the new admin')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, feeBps, mode, staging }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.updateVaultConfigIxs(
kaminoVault,
new ManagementFeeBps(),
feeBps,
signer
);
await processTx(
env.c,
signer,
[
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Management fee updated');
});
commands
.command('insert-into-lut')
.requiredOption('--lut <string>', 'Lookup table address')
.requiredOption('--addresses <string>', 'The addresses to insert into the LUT, space separated')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--signer <string>`, 'If set, it will use the provided signer instead of the default one')
.action(async ({ lut, addresses, mode, staging, multisig, signer }) => {
const env = await initEnv(multisig, staging);
const lutAddress = address(lut);
let txSigner = await env.getSigner();
// if the signer is provided (path to a keypair) we use it, otherwise we use the default one
if (signer) {
txSigner = await parseKeypairFile(signer as string);
}
const addressesArr = addresses.split(' ').map((a: string) => address(a));
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const instructions = await kaminoManager.insertIntoLutIxs(txSigner, lutAddress, addressesArr);
await processTx(
env.c,
txSigner,
[
...instructions,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Management fee updated');
});
commands.command('create-lut').action(async () => {
const env = await initEnv(false);
const signer = await env.getSigner();
const [initLutIx, lutAddress] = await initLookupTableIx(signer, await env.c.rpc.getSlot().send());
await processTx(
env.c,
signer,
[
initLutIx,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
'execute',
[]
);
console.log(`LUT created: ${lutAddress}`);
});
commands
.command('sync-vault-lut')
.requiredOption('--vault <string>', 'The vault address to sync')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--signer <string>`, 'If set, it will use the provided signer instead of the default one')
.action(async ({ vault, mode, staging, signer }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
let txSigner = await env.getSigner({ vaultState });
// if the signer is provided (path to a keypair) we use it, otherwise we use the default one
if (signer) {
txSigner = await parseKeypairFile(signer as string);
}
const syncLUTIxs = await kaminoManager.syncVaultLUTIxs(txSigner, 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) {
await processTx(
env.c,
txSigner,
[
...syncLUTIxs.setupLUTIfNeededIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
await sleep(2000);
mode === 'execute' && console.log('LUT created and set to the vault');
}
// 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) {
await processTx(
env.c,
txSigner,
[
ix,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Accounts added to the LUT');
}
});
commands
.command('update-vault-perf-fee')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--fee-bps <string>', 'Pubkey of the new admin')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, feeBps, mode, staging }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.updateVaultConfigIxs(
kaminoVault,
new PerformanceFeeBps(),
feeBps,
signer
);
await processTx(
env.c,
signer,
[
instructions.updateVaultConfigIx,
...instructions.updateLUTIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Performance fee updated');
});
commands
.command('accept-vault-ownership')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, mode, staging }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const pendingAdmin = await env.getSigner({
vaultState,
useVaultPendingAdmin: true,
});
const instructions = await kaminoManager.acceptVaultOwnershipIxs(kaminoVault, pendingAdmin);
await processTx(
env.c,
pendingAdmin,
[
instructions.acceptVaultOwnershipIx,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log(`Vault ownership accepted by ${pendingAdmin.address}`);
await processTx(
env.c,
pendingAdmin,
[
instructions.initNewLUTIx,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Initialized new LUT and updated vault config');
// 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);
await processTx(
env.c,
pendingAdmin,
[
...lutIxsGroup,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('LUT updated');
}
});
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|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, maxAmountToGiveUp, mode, staging, multisig }) => {
const env = await initEnv(multisig, staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instruction = await kaminoManager.giveUpPendingFeesIx(kaminoVault, new Decimal(maxAmountToGiveUp), signer);
await processTx(
env.c,
signer,
[
instruction,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Gave up pending fees');
});
commands
.command('withdraw-pending-fees')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, mode, staging }) => {
const env = await initEnv(staging);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const instructions = await kaminoManager.withdrawPendingFeesIxs(
kaminoVault,
await env.c.rpc.getSlot({ commitment: 'confirmed' }).send(),
signer
);
await processTx(
env.c,
signer,
[
...instructions,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Pending fees withdrawn');
});
commands
.command('remove-vault-allocation')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, reserve, mode, staging }) => {
const env = await initEnv(staging);
const reserveAddress = address(reserve);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const ixs = await kaminoManager.fullRemoveReserveFromVaultIxs(signer, kaminoVault, reserveAddress);
const transactionIxs = [
...ixs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 1_000_000,
}),
];
const lookupTableAddresses = [];
if (vaultState.vaultLookupTable !== DEFAULT_PUBLIC_KEY) {
lookupTableAddresses.push(vaultState.vaultLookupTable);
}
const lookupTables = await fetchAllAddressLookupTable(env.c.rpc, lookupTableAddresses);
await processTx(env.c, signer, transactionIxs, mode, lookupTables);
mode === 'execute' && console.log('Vault allocation removed');
});
commands
.command('stake')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.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 }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(staging, ms);
const user = await env.getSigner();
const vaultAddress = address(vault);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const stakeIxs = await new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
).stakeSharesIxs(user, kaminoVault);
await processTx(
env.c,
user,
[
...stakeIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Staked into vault farm');
});
commands
.command('update-vault-reserve-allocation')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option('--allocation-weight <number>', 'Allocation weight')
.option('--allocation-cap <string>', 'Allocation cap decimal value')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.option(`--skip-lut-update`, 'If set, it will skip the LUT update')
.action(async ({ vault, reserve, mode, allocationWeight, allocationCap, staging, multisig, skipLutUpdate }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(staging, ms);
const reserveAddress = address(reserve);
const vaultAddress = address(vault);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const signer = await env.getSigner({ vaultState });
const shouldUpdateLut = skipLutUpdate ? false : true;
let allocationWeightValue: number;
let allocationCapDecimal: Decimal;
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const reserveState = await Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
const existentAllocation = kaminoManager.getVaultAllocations(vaultState).get(reserveAddress);
if (allocationWeight) {
allocationWeightValue = Number(allocationWeight);
} else if (existentAllocation) {
allocationWeightValue = existentAllocation.targetWeight.toNumber();
} else {
throw new Error('Allocation weight is required');
}
if (allocationCap) {
allocationCapDecimal = new Decimal(allocationCap);
} else if (existentAllocation) {
allocationCapDecimal = existentAllocation.tokenAllocationCap.div(
new Decimal(10).pow(Number(vaultState.tokenMintDecimals.toString()))
);
} else {
throw new Error('Allocation cap is required');
}
console.log('allocationWeightValue', allocationWeightValue);
console.log('allocationCapDecimal', allocationCapDecimal.toString());
const reserveWithAddress: ReserveWithAddress = {
address: reserveAddress,
state: reserveState,
};
const firstReserveAllocationConfig = new ReserveAllocationConfig(
reserveWithAddress,
allocationWeightValue,
allocationCapDecimal
);
const instructions = await kaminoManager.updateVaultReserveAllocationIxs(
kaminoVault,
firstReserveAllocationConfig,
signer
);
const txInstructions = [
instructions.updateReserveAllocationIx,
...instructions.updateLUTIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
}),
];
if (shouldUpdateLut) {
txInstructions.push(...instructions.updateLUTIxs);
}
await processTx(env.c, signer, txInstructions, mode, []);
mode === 'execute' && console.log('Vault allocation updated');
});
commands
.command('deposit')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--amount <number>', 'Token amount to deposit, in decimals')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ vault, amount, mode, staging, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const env = await initEnv(staging, multisig);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const payer = await env.getSigner();
const depositInstructions = await kaminoManager.depositToVaultIxs(payer, kaminoVault, amount);
const instructions = [...depositInstructions.depositIxs, ...depositInstructions.stakeInFarmIfNeededIxs];
await processTx(
env.c,
payer,
[
...instructions,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 800_000,
}),
],
mode,
[]
);
mode === 'execute' && console.log('User deposited');
});
commands
.command('withdraw')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--amount <number>', 'Shares amount to withdraw')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.option(`--staging`, 'If true, will use the staging programs')
.option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
.action(async ({ vault, amount, mode, staging, multisig }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const env = await initEnv(multisig, staging);
const signer = await env.getSigner();
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultState = await kaminoVault.getState();
const lookupTableAddresses = [];
if (vaultState.vaultLookupTable !== DEFAULT_PUBLIC_KEY) {
lookupTableAddresses.push(vaultState.vaultLookupTable);
}
const lookupTables = await fetchAllAddressLookupTable(env.c.rpc, lookupTableAddresses);
const withdrawIxs = await kaminoManager.withdrawFromVaultIxs(
signer,
kaminoVault,
new Decimal(amount),
await env.c.rpc.getSlot({ commitment: 'confirmed' }).send()
);
await processTx(
env.c,
signer,
[
...withdrawIxs.unstakeFromFarmIfNeededIxs,
...withdrawIxs.withdrawIxs,
...withdrawIxs.postWithdrawIxs,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 800_000,
}),
],
mode,
lookupTables
);
mode === 'execute' && console.log('User withdrew');
});
commands
.command('invest-all-reserves')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.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 }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(staging, ms);
const payer = await env.getSigner();
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const instructions = await kaminoManager.investAllReservesIxs(payer, kaminoVault);
for (let i = 0; i < instructions.length; i++) {
const txInstructions: Instruction[] = [];
txInstructions.push();
await processTx(
env.c,
payer,
[
instructions[i],
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 800_000,
}),
],
mode,
[]
);
mode === 'execute' && console.log('Reserves invested');
}
});
commands
.command('invest-single-reserve')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--reserve <string>', 'Reserve address')
.requiredOption(
`--mode <string>`,
'simulate|multisig|execute - simulate - to print txn simulation and to get tx simulation link in explorer, execute - execute tx, multisig - to get bs58 tx for multisig usage'
)
.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 }) => {
if (mode === 'multisig' && !multisig) {
throw new Error('If using multisig mode, multisig is required');
}
const ms = multisig ? address(multisig) : undefined;
const env = await initEnv(staging, ms);
const vaultAddress = address(vault);
const kaminoManager = new KaminoManager(
env.c.rpc,
DEFAULT_RECENT_SLOT_DURATION_MS,
env.klendProgramId,
env.kvaultProgramId
);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const reserveAddress = address(reserve);
const reserveState = await Reserve.fetch(env.c.rpc, reserveAddress, env.klendProgramId);
if (!reserveState) {
throw new Error('Reserve not found');
}
const reserveWithAddress: ReserveWithAddress = {
address: reserveAddress,
state: reserveState,
};
const payer = await env.getSigner();
const instructions = await kaminoManager.investSingleReserveIxs(payer, kaminoVault, reserveWithAddress);
await processTx(
env.c,
payer,
[
...instructions,
...getPriorityFeeAndCuIxs({
priorityFeeMultiplier: 2500,
computeUnits: 800_000,
}),
],
mode,
[]
);
mode === 'execute' && console.log(`Reserve ${reserveAddress} invested`);
});
// commands
// .command('close-vault')
// .requiredOption('--vault <string>', 'Vault address')
// .option(`--staging`, 'If true, will use the staging programs')
// .action(async ({vault, staging}) => {
// const env = await initEnv(false, staging);
// const vaultAddress = address(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')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, staging }) => {
const env = await initEnv(staging);
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(env.c.rpc, slotDuration, env.klendProgramId, env.kvaultProgramId);
const vaultAddress = address(vault);
const vaultState = await new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId).getState();
const vaultCollaterals = await kaminoManager.getVaultCollaterals(
vaultState,
await env.c.rpc.getSlot({ commitment: 'confirmed' }).send()
);
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')
.option(`--staging`, 'If true, will use the staging programs')
.option(`--token-price <number>`, 'Vault token price in USD')
.action(async ({ vault, staging, tokenPrice }) => {
const env = await initEnv(staging);
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(env.c.rpc, slotDuration, env.klendProgramId, env.kvaultProgramId);
const vaultAddress = address(vault);
const kaminoVault = new KaminoVault(env.c.rpc, vaultAddress, undefined, env.kvaultProgramId);
const vaultOverview = await kaminoManager.getVaultOverview(
kaminoVault,
new Decimal(tokenPrice),
await env.c.rpc.getSlot({ commitment: 'confirmed' }).send()
);
console.log('vaultOverview', vaultOverview);
vaultOverview.reservesFarmsIncentives.reserveFarmsIncentives.forEach((incentive, reserveAddress) => {
console.log('reserve ', reserveAddress);
console.log('reserve incentive', incentive);
});
console.log('totalIncentivesAPY', vaultOverview.reservesFarmsIncentives.totalIncentivesAPY.toString());
});
commands
.command('get-vault-farm-apy')
.requiredOption('--vault <string>', 'Vault address')
.requiredOption('--token-price <number>', 'Vault token price in USD')
.option(`--staging`, 'If true, will use the staging programs')
.action(async ({ vault, tokenPrice, staging }) => {
const env = await initEnv(staging);
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(env.c.rpc, slotDuration, env.klendProgramId, env.kvaultProgramId);
const kaminoVault = new KaminoVault(env.c.rpc, address(vault), undefined, env.kvaultProgramId, slotDuration);
const farmAPY = await kaminoManager.getVaultFarmRewardsAPY(kaminoVault, new Decimal(tokenPrice));
console.log('farmAPY', farmAPY);
});
commands