@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
372 lines • 18 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnstakingPool = exports.UnstakingPoolClient = exports.BASE_POOL_AUTHORITY_SEED = exports.UNSTAKING_SOL_MINT_SEED = exports.STAKE_POOL_SIZE = exports.CLOCK_PROGRAM_ID = exports.STAKE_PROGRAM_ID = exports.UNSTAKING_POOL_STAGING_ID = void 0;
exports.unstakingPoolMintPda = unstakingPoolMintPda;
exports.unstakingPoolAuthorityPda = unstakingPoolAuthorityPda;
const instructions_1 = require("../@codegen/unstaking_pool/instructions");
const token_2022_1 = require("@solana-program/token-2022");
const lib_1 = require("../lib");
const accounts_1 = require("../@codegen/unstaking_pool/accounts");
const utils_1 = require("../utils");
const bs58_1 = __importDefault(require("bs58"));
const rpc_1 = require("../utils/rpc");
const types_1 = require("../@codegen/unstaking_pool/types");
const bn_js_1 = __importDefault(require("bn.js"));
const stakePool_1 = require("./stakePool");
const standardStakePool_1 = require("./standardStakePool");
const kit_1 = require("@solana/kit");
const system_1 = require("@solana-program/system");
const sysvars_1 = require("@solana/sysvars");
const token_1 = require("@solana-program/token");
const compat_1 = require("@solana/compat");
const programId_1 = require("../@codegen/unstaking_pool/programId");
exports.UNSTAKING_POOL_STAGING_ID = (0, kit_1.address)('SUPFzSvjWnK9AbQ5bQksKaDKeAZBx56Gtjx1AjJsUdj');
exports.STAKE_PROGRAM_ID = (0, kit_1.address)('Stake11111111111111111111111111111111111111');
exports.CLOCK_PROGRAM_ID = (0, kit_1.address)('SysvarC1ock11111111111111111111111111111111');
const STAKE_HISTORY_PROGRAM_ID = (0, kit_1.address)('SysvarStakeHistory1111111111111111111111111');
const STAKE_ACCOUNT_SIZE = 200;
exports.STAKE_POOL_SIZE = 611;
const addressEncoder = (0, kit_1.getAddressEncoder)();
/**
* Unstaking sol mint seed
*/
exports.UNSTAKING_SOL_MINT_SEED = Buffer.from('unstaking_sol_mint');
/**
* Unstaking sol pool base authority seed
*/
exports.BASE_POOL_AUTHORITY_SEED = Buffer.from('authority');
/**
* KaminoPoolClient is a class that provides a high-level interface to interact with the Kamino Pool program.
*/
class UnstakingPoolClient {
_rpc;
_unstakingPoolProgramId;
constructor(rpc, unstakingPoolprogramId) {
this._rpc = rpc;
this._unstakingPoolProgramId = unstakingPoolprogramId ? unstakingPoolprogramId : programId_1.PROGRAM_ID;
}
getConnection() {
return this._rpc;
}
getProgramID() {
return this._unstakingPoolProgramId;
}
/**
* This method will create a pool with a given config. The config can be changed later on, but it is recommended to set it up correctly from the start
* @param poolConfig - the config object used to create a pool
* @returns pool - keypair, should be used to sign the transaction which creates the pool account
* @returns pool: the keypair of the pool, used to sign the initialization transaction; initPoolIxs: a struct with ixs to initialize the pool and its lookup table + populateLUTIxs, a list to populate the lookup table which has to be executed in a separate transaction
*/
async createPoolIxs(poolConfig) {
const poolState = await (0, kit_1.generateKeyPairSigner)();
const size = accounts_1.PoolState.layout.span + 8;
const createPoolIx = (0, system_1.getCreateAccountInstruction)({
payer: poolConfig.admin,
newAccount: poolState,
lamports: await this.getConnection().getMinimumBalanceForRentExemption(BigInt(size)).send(),
space: size,
programAddress: this._unstakingPoolProgramId,
});
const unstakingSolMint = (await unstakingPoolMintPda(poolState.address))[0];
const basePoolAuthority = (await unstakingPoolAuthorityPda(poolState.address))[0];
const wsolVault = await (0, lib_1.getAssociatedTokenAddress)(utils_1.WRAPPED_SOL_MINT, basePoolAuthority);
const initPoolAccounts = {
admin: poolConfig.admin,
poolState: poolState.address,
basePoolAuthority,
systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
rent: sysvars_1.SYSVAR_RENT_ADDRESS,
tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
unstakingSolMint,
wsolMint: utils_1.WRAPPED_SOL_MINT,
wsolVault,
};
const initPoolIx = (0, instructions_1.initializePool)(initPoolAccounts, this._unstakingPoolProgramId);
// create and set up the pool lookup table
const [createLUTIx, lut] = await (0, utils_1.createLookupTableIx)(this.getConnection(), poolConfig.admin);
const allAccountsToBeInserted = [
poolState.address,
basePoolAuthority,
wsolVault,
unstakingSolMint,
poolConfig.admin.address,
utils_1.WRAPPED_SOL_MINT,
this._unstakingPoolProgramId,
system_1.SYSTEM_PROGRAM_ADDRESS,
sysvars_1.SYSVAR_RENT_ADDRESS,
token_1.TOKEN_PROGRAM_ADDRESS,
token_2022_1.TOKEN_2022_PROGRAM_ADDRESS,
sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
sysvars_1.SYSVAR_CLOCK_ADDRESS,
exports.STAKE_PROGRAM_ID,
standardStakePool_1.STAKE_POOL_PROGRAM_ID,
];
const insertIntoLUTIxs = (0, utils_1.extendLookupTableIxs)(poolConfig.admin, lut, allAccountsToBeInserted, poolConfig.admin);
const updateLUTIx = await this.updatePoolConfigIxs(poolState.address, poolConfig.admin, new types_1.PoolConfigField.LookupTable(), lut.toString());
const ixns = [createPoolIx, initPoolIx, createLUTIx, ...insertIntoLUTIxs, updateLUTIx];
if (poolConfig.actionAuthority) {
const updateActionAuthorityIx = await this.updatePoolConfigIxs(poolState.address, poolConfig.admin, new types_1.PoolConfigField.ActionAuthority(), poolConfig.actionAuthority.toString());
ixns.push(updateActionAuthorityIx);
}
return { pool: poolState, initPoolIxs: { initPoolIxs: ixns, populateLUTIxs: [] } };
}
/**
* Update pool configuration such as admin authority (or fees/minimum depositable in the future)
* @param poolState - the pool to update and set the LUT for if needed or only the pool pubkey if updating LUT is not needed
* @param admin - admin of the specified pool
* @param mode - what field to update for pool
* @param value - new value that is converted .toString()
* @returns a struct that contains a list of ix to update the pool config
*/
async updatePoolConfigIxs(poolState, admin, mode, value) {
const updatePoolConfigAccounts = {
admin,
poolState: poolState instanceof UnstakingPool ? poolState.address : poolState,
};
const args = {
entry: mode,
data: Buffer.from([0]),
};
if (isNaN(+value)) {
const data = (0, kit_1.address)(value);
args.data = Buffer.from(addressEncoder.encode(data));
}
else {
const buffer = Buffer.alloc(8);
buffer.writeBigUInt64LE(BigInt(value.toString()));
args.data = buffer;
}
const updatePoolConfigIx = (0, instructions_1.updatePoolConfig)(args, updatePoolConfigAccounts, this._unstakingPoolProgramId);
return updatePoolConfigIx;
}
/**
* Collect a stake account SOL if the needed epoch was reached
* @param poolState - the pool to collect SOL into
* @param payer - payer for the operation (ix is permissionless)
* @param stakeAccount - stake account that was deactivated this epoch and has base pool authority as owner
* @returns collect instruction
*/
async collectIx(poolState, payer, stakeAccount) {
const pool = await poolState.getState(this.getConnection());
const accounts = {
poolState: poolState.address,
payer,
stakeAccount,
basePoolAuthority: pool.basePoolAuthority,
wsolVault: pool.wsolVault,
wsolMint: utils_1.WRAPPED_SOL_MINT,
tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
clockProgramId: sysvars_1.SYSVAR_CLOCK_ADDRESS,
stakeProgramId: exports.STAKE_PROGRAM_ID,
stakeHistoryProgramId: STAKE_HISTORY_PROGRAM_ID,
};
return (0, instructions_1.collect)(accounts, this._unstakingPoolProgramId);
}
/**
* Burn a number of shares (USOL) in exchange for SOL
* @param poolState - the pool to burn USOL from
* @param user - user that burns (ix is not gated by action authority)
* @param unstakeTicket - ticket where to burn the shares from
* @param sharesToBurn - number of shares that are equivalent 1:1 with SOL
* @returns burn instruction
*/
async burnIx(poolState, user, unstakeTicket, sharesToBurn) {
const pool = await poolState.getState(this.getConnection());
const accounts = {
poolState: poolState.address,
basePoolAuthority: pool.basePoolAuthority,
wsolVault: pool.wsolVault,
wsolMint: utils_1.WRAPPED_SOL_MINT,
user,
userWsolToken: await (0, lib_1.getAssociatedTokenAddress)(utils_1.WRAPPED_SOL_MINT, user.address),
userUnstakingSolToken: await (0, lib_1.getAssociatedTokenAddress)(pool.unstakingSolMint, user.address),
unstakingSolMint: pool.unstakingSolMint,
tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
unstakeTicket,
};
const args = {
sharesToBurn,
minWsolToReceive: sharesToBurn,
};
return (0, instructions_1.burn)(args, accounts, this._unstakingPoolProgramId);
}
/**
* Mints a number of unstaking sol (USOL) in exchange for staked SOL
* NOTE: this ix is permissioned by action authority
* @param poolState - the pool to mint USOL from
* @param user - user that mints
* @param actionAuthority - user that has authority to mint in that pool (== poolState.actionAuthority)
* @param unstakeTicket - empty keypair where unstake ticket will be stored
* @param stakedSolMint - staked sol mint
* @param stakedSolToDeposit - staked sol to convert to USOL (at the pool ratio)
* @param minSharesToReceive - parameter to control slippage
* @returns burn instruction
*/
async mintIx(poolState, user, actionAuthority, unstakeTicket, stakedSolMint, stakedSolToDeposit, minSharesToReceive) {
const pool = await poolState.getState(this.getConnection());
// Create unstake ticket ix
const size = accounts_1.UnstakeTicket.layout.span + 8;
const createUnstakeTicketIx = (0, system_1.getCreateAccountInstruction)({
payer: user,
newAccount: unstakeTicket,
lamports: await this.getConnection().getMinimumBalanceForRentExemption(BigInt(size)).send(),
space: size,
programAddress: this._unstakingPoolProgramId,
});
// Actual mint ix
const [stakedSolPool, stakedSolPoolPk, stakePoolType] = await (0, stakePool_1.mapStakedSolMintToPool)(this.getConnection(), stakedSolMint);
const accounts = {
poolState: poolState.address,
basePoolAuthority: pool.basePoolAuthority,
systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
unstakingSolMint: pool.unstakingSolMint,
unstakingSolTokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
user,
actionAuthority,
userStakedSolToken: await (0, lib_1.getAssociatedTokenAddress)(stakedSolMint, user.address),
userUnstakingSolToken: await (0, lib_1.getAssociatedTokenAddress)(pool.unstakingSolMint, user.address),
stakedSolMint,
stakedSolTokenProgram: (0, compat_1.fromLegacyPublicKey)(stakedSolPool.tokenProgramId),
unstakingTicketAuthority: user.address,
unstakeTicket: unstakeTicket.address,
};
const args = {
stakedSolToDeposit,
minSharesToReceive,
};
const ix = (0, instructions_1.mint)(args, accounts, this._unstakingPoolProgramId);
let remainingAccounts = [];
let remainingSigners = [];
switch (stakePoolType) {
case stakePool_1.StakePoolType.Standard:
[remainingAccounts, remainingSigners] = await (0, standardStakePool_1.getStandardPoolMintRemainingAccounts)(this.getConnection(), stakedSolPool, stakedSolPoolPk, stakedSolToDeposit);
}
const ixAccounts = ix.accounts || [];
const mintIx = {
programAddress: ix.programAddress,
accounts: ixAccounts.concat(remainingAccounts),
data: ix.data,
};
return { mintIxs: [createUnstakeTicketIx, mintIx], additionalSigners: remainingSigners };
}
/**
* Sync a pool for lookup table;
* @param pool the pool to sync the LUT for
* @param owner the pool lut owner
* @returns a struct that contains a list of ix to create the LUT and assign it to the pool if needed + a list of ixs to insert all the accounts in the LUT
*/
async syncPoolLookupTable(pool, owner) {
const poolState = await pool.getState(this.getConnection());
if (poolState.poolLookupTable == utils_1.DEFAULT_PUBLIC_KEY) {
throw new Error(`Pool ${pool.address} has no lut set`);
}
const allAccountsToBeInserted = [
pool.address,
poolState.basePoolAuthority,
poolState.wsolVault,
poolState.unstakingSolMint,
poolState.actionAuthority,
poolState.admin,
this._unstakingPoolProgramId,
system_1.SYSTEM_PROGRAM_ADDRESS,
sysvars_1.SYSVAR_RENT_ADDRESS,
token_1.TOKEN_PROGRAM_ADDRESS,
token_2022_1.TOKEN_2022_PROGRAM_ADDRESS,
sysvars_1.SYSVAR_INSTRUCTIONS_ADDRESS,
sysvars_1.SYSVAR_CLOCK_ADDRESS,
exports.STAKE_PROGRAM_ID,
standardStakePool_1.STAKE_POOL_PROGRAM_ID,
];
// Passing [] as accountsInLut will not fetch anything
const syncIxs = (0, utils_1.insertIntoLookupTableIxs)(this.getConnection(), owner, poolState.poolLookupTable, allAccountsToBeInserted, []);
return syncIxs;
}
/**
* Get all pools
* @returns an array of all pools
*/
async getAllPools() {
const filters = [
{
dataSize: BigInt(accounts_1.PoolState.layout.span + 8),
},
{
memcmp: {
offset: 0n,
bytes: bs58_1.default.encode(accounts_1.PoolState.discriminator),
encoding: 'base58',
},
},
];
const unstakingPools = await (0, rpc_1.getProgramAccounts)(this.getConnection(), this._unstakingPoolProgramId, accounts_1.PoolState.layout.span + 8, filters);
return unstakingPools.map((unstakingPool) => {
const unstakingPoolAccount = accounts_1.PoolState.decode(unstakingPool.data);
if (!unstakingPoolAccount) {
throw Error(`unstakingPool with pubkey ${unstakingPool.address.toString()} could not be decoded`);
}
return new UnstakingPool(unstakingPool.address, unstakingPoolAccount, this._unstakingPoolProgramId);
});
}
} // UnstakingPoolClient
exports.UnstakingPoolClient = UnstakingPoolClient;
class UnstakingPool {
address;
state;
programId;
constructor(poolAddress, state, programId = programId_1.PROGRAM_ID) {
this.address = poolAddress;
this.state = state;
this.programId = programId;
}
async getState(rpc) {
if (!this.state) {
this.state = await this.reloadState(rpc);
}
return this.state;
}
async reloadState(rpc) {
this.state = await accounts_1.PoolState.fetch(rpc, this.address, this.programId);
if (!this.state) {
throw new Error(`Could not fetch pool ${this.address.toString()}`);
}
return this.state;
}
async getStakeAccountsForPool(rpc) {
if (!this.state) {
throw new Error('Need to have pool state to fetch stake accounts');
}
// Filter only accounts that have withdraw authority the base pool authority
// and are delegating
const results = await (0, rpc_1.getProgramAccounts)(rpc, exports.STAKE_PROGRAM_ID, STAKE_ACCOUNT_SIZE, [
{ memcmp: { offset: 0n, bytes: bs58_1.default.encode([2]), encoding: 'base58' } },
{
memcmp: {
offset: 44n,
bytes: this.state.basePoolAuthority.toString(),
encoding: 'base58',
},
},
]);
return results.map((result) => {
return { stakeAccount: standardStakePool_1.StakeAccount.decode(result.data), pk: result.address, lamports: new bn_js_1.default(result.lamports) };
});
}
}
exports.UnstakingPool = UnstakingPool;
function unstakingPoolMintPda(pool, programId = programId_1.PROGRAM_ID) {
return (0, kit_1.getProgramDerivedAddress)({
seeds: [exports.UNSTAKING_SOL_MINT_SEED, addressEncoder.encode(pool)],
programAddress: programId,
});
}
function unstakingPoolAuthorityPda(pool, programId = programId_1.PROGRAM_ID) {
return (0, kit_1.getProgramDerivedAddress)({
seeds: [exports.BASE_POOL_AUTHORITY_SEED, addressEncoder.encode(pool)],
programAddress: programId,
});
}
//# sourceMappingURL=unstakingPool.js.map