@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
169 lines (155 loc) • 5.57 kB
text/typescript
import { Address, IInstruction, address, TransactionSigner, Rpc, GetAccountInfoApi, Account } from '@solana/kit';
import { WhirlpoolStrategy } from '../@codegen/kliquidity/accounts';
import Decimal from 'decimal.js';
import { CollateralInfo } from '../@codegen/kliquidity/types';
import { getPriceOfBinByBinIdWithDecimals } from './meteora';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import {
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
fetchMaybeMint,
fetchMaybeToken,
findAssociatedTokenPda,
getCreateAssociatedTokenInstruction,
Token,
} from '@solana-program/token-2022';
import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
import { getSetComputeUnitLimitInstruction } from '@solana-program/compute-budget';
import { tickIndexToPrice } from '@orca-so/whirlpools-core';
export const SOL_MINTS = [
address('So11111111111111111111111111111111111111111'),
address('So11111111111111111111111111111111111111112'),
];
export const DECIMALS_SOL = 9;
export async function getAssociatedTokenAddressAndAccount(
connection: Rpc<GetAccountInfoApi>,
mint: Address,
owner: Address,
programId: Address = TOKEN_PROGRAM_ADDRESS
): Promise<[Address, Account<Token> | null]> {
const ata = await getAssociatedTokenAddress(mint, owner, programId);
const account = await fetchMaybeToken(connection, ata);
return [ata, account.exists ? account : null];
}
export async function getAssociatedTokenAddress(
mint: Address,
owner: Address,
programId: Address = TOKEN_PROGRAM_ADDRESS,
associatedTokenProgramId: Address = ASSOCIATED_TOKEN_PROGRAM_ADDRESS
): Promise<Address> {
const [ata] = await findAssociatedTokenPda(
{
mint,
owner,
tokenProgram: programId,
},
{ programAddress: associatedTokenProgramId }
);
return ata;
}
export function createAssociatedTokenAccountInstruction(
payer: TransactionSigner,
associatedTokenAddress: Address,
owner: Address,
mint: Address,
programId: Address = TOKEN_PROGRAM_ADDRESS,
associatedTokenProgramId: Address = ASSOCIATED_TOKEN_PROGRAM_ADDRESS
): IInstruction {
return getCreateAssociatedTokenInstruction(
{
mint,
owner,
ata: associatedTokenAddress,
payer: payer,
tokenProgram: programId,
systemProgram: SYSTEM_PROGRAM_ADDRESS,
},
{ programAddress: associatedTokenProgramId }
);
}
export function createComputeUnitLimitIx(units: number = 400000): IInstruction {
return getSetComputeUnitLimitInstruction({ units });
}
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 = new Decimal(tickIndexToPrice(tickCurrent, tokenADecimals, tokenBDecimals));
const strategyOutOfRange = poolPrice.lt(priceLower) || poolPrice.gt(priceUpper);
return { priceLower: new Decimal(priceLower), poolPrice, priceUpper: new Decimal(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: Address): boolean => {
return SOL_MINTS.filter((m) => m === mint).length > 0;
};
export async function getMintDecimals(rpc: Rpc<GetAccountInfoApi>, mint: Address): Promise<number> {
if (isSOLMint(mint)) {
return DECIMALS_SOL;
}
const acc = await fetchMaybeMint(rpc, mint);
if (!acc.exists) {
throw new Error(`Mint ${mint} not found`);
}
return acc.data.decimals;
}