UNPKG

@kamino-finance/klend-sdk

Version:

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

372 lines 18 kB
"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