UNPKG

@kamino-finance/kliquidity-sdk

Version:

Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol

231 lines (206 loc) 7.51 kB
import { AccountInfo, Commitment, ComputeBudgetProgram, Connection, PublicKey, sendAndConfirmTransaction, Signer, SystemProgram, Transaction, TransactionInstruction, TransactionSignature, } from '@solana/web3.js'; import { sleep } from './utils'; import { WhirlpoolStrategy } from '../kamino-client/accounts'; import { tickIndexToPrice } from '@orca-so/whirlpool-sdk'; import Decimal from 'decimal.js'; import { CollateralInfo } from '../kamino-client/types'; import { getPriceOfBinByBinIdWithDecimals } from './meteora'; import { getAssociatedTokenAddress as getAta, unpackMint } from '@solana/spl-token'; export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); export const SOL_MINTS = [ new PublicKey('So11111111111111111111111111111111111111111'), new PublicKey('So11111111111111111111111111111111111111112'), ]; export const DECIMALS_SOL = 9; export async function getAssociatedTokenAddressAndData( connection: Connection, mint: PublicKey, owner: PublicKey, programId = TOKEN_PROGRAM_ID ): Promise<[PublicKey, AccountInfo<Buffer> | null]> { const ata = await getAssociatedTokenAddress(mint, owner, true, programId); const data = await connection.getAccountInfo(ata); return [ata, data]; } export function getAssociatedTokenAddress( mint: PublicKey, owner: PublicKey, allowOwnerOffCurve = true, programId = TOKEN_PROGRAM_ID, associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID ): Promise<PublicKey> { return getAta(mint, owner, allowOwnerOffCurve, programId, associatedTokenProgramId); } export function createAssociatedTokenAccountInstruction( payer: PublicKey, associatedToken: PublicKey, owner: PublicKey, mint: PublicKey, programId = TOKEN_PROGRAM_ID, associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID ): TransactionInstruction { const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: associatedToken, isSigner: false, isWritable: true }, { pubkey: owner, isSigner: false, isWritable: false }, { pubkey: mint, isSigner: false, isWritable: false }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, { pubkey: programId, isSigner: false, isWritable: false }, ]; return new TransactionInstruction({ keys, programId: associatedTokenProgramId, data: Buffer.alloc(0), }); } export function createAddExtraComputeUnitsIx(units: number): TransactionInstruction { return ComputeBudgetProgram.setComputeUnitLimit({ units }); } export function createTransactionWithExtraBudget(extraUnits: number = 400000) { return new Transaction().add(createAddExtraComputeUnitsIx(extraUnits)); } export async function assignBlockInfoToTransaction(connection: Connection, transaction: Transaction, payer: PublicKey) { const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = payer; transaction.lastValidBlockHeight = lastValidBlockHeight; return transaction; } export async function sendTransactionWithLogs( connection: Connection, tx: Transaction, payer: PublicKey, signers: Signer[], commitment: Commitment = 'processed', skipPreflight: boolean = false ): Promise<TransactionSignature | null> { const txn = await assignBlockInfoToTransaction(connection, tx, payer); try { const res = await sendAndConfirmTransaction(connection, txn, signers, { commitment: commitment, skipPreflight: skipPreflight, }); return res; } catch (e) { console.log('ERROR:', e); await sleep(5000); // @ts-ignore const sig = e.toString().split(' failed ')[0].split('Transaction ')[1]; const res = await connection.getTransaction(sig, { commitment: 'confirmed' }); if (res && res.meta) { console.log('Txn', res.meta.logMessages); } return null; } } export function getStrategyPriceRangeOrca( tickLowerIndex: number, tickUpperIndex: number, strategy: WhirlpoolStrategy, poolPrice: Decimal ) { const { priceLower, priceUpper } = getPriceLowerUpper( tickLowerIndex, tickUpperIndex, Number(strategy.tokenAMintDecimals.toString()), Number(strategy.tokenBMintDecimals.toString()) ); const strategyOutOfRange = poolPrice.lt(priceLower) || poolPrice.gt(priceUpper); return { priceLower, poolPrice, priceUpper, strategyOutOfRange }; } export function getStrategyPriceRangeRaydium( tickLowerIndex: number, tickUpperIndex: number, tickCurrent: number, tokenADecimals: number, tokenBDecimals: number ) { const { priceLower, priceUpper } = getPriceLowerUpper(tickLowerIndex, tickUpperIndex, tokenADecimals, tokenBDecimals); const poolPrice = tickIndexToPrice(tickCurrent, tokenADecimals, tokenBDecimals); const strategyOutOfRange = poolPrice.lt(priceLower) || poolPrice.gt(priceUpper); return { priceLower, poolPrice, priceUpper, strategyOutOfRange }; } export function getStrategyPriceRangeMeteora( priceLower: Decimal, priceUpper: Decimal, activeBinId: number, binStep: number, decimalsA: number, decimalsB: number ) { const poolPrice = getPriceOfBinByBinIdWithDecimals(activeBinId, binStep, decimalsA, decimalsB); const strategyOutOfRange = poolPrice.lt(priceLower) || poolPrice.gt(priceUpper); return { priceLower, poolPrice, priceUpper, strategyOutOfRange }; } export function getMeteoraPriceLowerUpper( tickLowerIndex: number, tickUpperIndex: number, tokenAMintDecimals: number, tokenBMintDecimals: number, binStep: number ) { const priceLower = getPriceOfBinByBinIdWithDecimals(tickLowerIndex, binStep, tokenAMintDecimals, tokenBMintDecimals); const priceUpper = getPriceOfBinByBinIdWithDecimals(tickUpperIndex, binStep, tokenAMintDecimals, tokenBMintDecimals); return { priceLower, priceUpper }; } export function getPriceLowerUpper( tickLowerIndex: number, tickUpperIndex: number, tokenAMintDecimals: number, tokenBMintDecimals: number ) { const priceLower = tickIndexToPrice( tickLowerIndex, Number(tokenAMintDecimals.toString()), Number(tokenBMintDecimals.toString()) ); const priceUpper = tickIndexToPrice( tickUpperIndex, Number(tokenAMintDecimals.toString()), Number(tokenBMintDecimals.toString()) ); return { priceLower, priceUpper }; } export function getTokenNameFromCollateralInfo(collateralInfo: CollateralInfo) { return String.fromCharCode(...collateralInfo.name.filter((x) => x > 0)); } export const isSOLMint = (mint: PublicKey): boolean => { return SOL_MINTS.filter((m) => m.equals(mint)).length > 0; }; export async function getMintDecimals(connection: Connection, mint: PublicKey): Promise<number> { if (isSOLMint(mint)) { return DECIMALS_SOL; } const acc = await connection.getAccountInfo(mint); if (!acc) { throw new Error(`Mint ${mint.toBase58()} not found`); } const mintInfo = unpackMint(mint, acc, acc.owner); return mintInfo.decimals; } export function removeBudgetAndAtaIxns(ixns: TransactionInstruction[], mints: PublicKey[]): TransactionInstruction[] { return ixns.filter((ixn) => { const { programId, keys } = ixn; if (programId.equals(ComputeBudgetProgram.programId)) { return false; } if (programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) { const mint = keys[3]; return !mints.includes(mint.pubkey); } return true; }); }