@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
464 lines • 22 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RaydiumService = void 0;
exports.getPdaExBitmapAccount = getPdaExBitmapAccount;
const kit_1 = require("@solana/kit");
const accounts_1 = require("../@codegen/raydium/accounts");
const decimal_js_1 = __importDefault(require("decimal.js"));
const utils_1 = require("../utils");
const axios_1 = __importDefault(require("axios"));
const CreationParameters_1 = require("../utils/CreationParameters");
const programId_1 = require("../@codegen/raydium/programId");
const raydium_1 = require("../utils/raydium");
const lib_1 = require("@raydium-io/raydium-sdk-v2/lib");
const compat_1 = require("@solana/compat");
const token_2022_1 = require("@solana-program/token-2022");
const pubkeys_1 = require("../constants/pubkeys");
class RaydiumService {
_rpc;
_raydiumProgramId;
constructor(rpc, raydiumProgramId = programId_1.PROGRAM_ID) {
this._rpc = rpc;
this._raydiumProgramId = raydiumProgramId;
}
getRaydiumProgramId() {
return this._raydiumProgramId;
}
async getRaydiumWhirlpools() {
return (await axios_1.default.get(`https://api.kamino.finance/v2/raydium/ammPools`)).data;
}
async getRaydiumPoolInfo(poolPubkey) {
return this.getPoolInfoFromRpc(poolPubkey.toString());
}
async getRaydiumPoolLiquidityDistribution(pool, keepOrder = true, lowestTick, highestTick) {
const raydiumLiqDistribution = (await axios_1.default.get(`https://api.kamino.finance/v2/raydium/positionLine/${pool.toString()}`)).data;
const poolState = await accounts_1.PoolState.fetch(this._rpc, pool);
if (!poolState) {
throw Error(`Raydium pool state ${pool} does not exist`);
}
const poolPrice = lib_1.SqrtPriceMath.sqrtPriceX64ToPrice(poolState.sqrtPriceX64, poolState.mintDecimals0, poolState.mintDecimals1);
const liqDistribution = {
currentPrice: poolPrice,
currentTickIndex: poolState.tickCurrent,
distribution: [],
};
raydiumLiqDistribution.data.forEach((entry) => {
const tickIndex = (0, raydium_1.priceToTickIndexWithRounding)(entry.price);
if ((lowestTick && tickIndex < lowestTick) || (highestTick && tickIndex > highestTick)) {
return;
}
// if the prevoious entry has the same tick index, add to it
if (liqDistribution.distribution.length > 0 &&
liqDistribution.distribution[liqDistribution.distribution.length - 1].tickIndex === tickIndex) {
liqDistribution.distribution[liqDistribution.distribution.length - 1].liquidity = liqDistribution.distribution[liqDistribution.distribution.length - 1].liquidity.add(new decimal_js_1.default(entry.liquidity));
}
else {
let priceWithOrder = new decimal_js_1.default(entry.price);
if (!keepOrder) {
priceWithOrder = new decimal_js_1.default(1).div(priceWithOrder);
}
const liq = {
price: new decimal_js_1.default(priceWithOrder),
liquidity: new decimal_js_1.default(entry.liquidity),
tickIndex,
};
liqDistribution.distribution.push(liq);
}
});
return liqDistribution;
}
getStrategyWhirlpoolPoolAprApy = async (strategy, pools) => {
const position = await accounts_1.PersonalPositionState.fetch(this._rpc, strategy.position);
if (!position) {
throw Error(`Position ${strategy.position} does not exist`);
}
const poolState = await accounts_1.PoolState.fetch(this._rpc, strategy.pool);
if (!poolState) {
throw Error(`Raydium pool state ${strategy.pool} does not exist`);
}
if (!pools) {
({ data: pools } = await this.getRaydiumWhirlpools());
}
if (!pools || pools.length === 0) {
throw Error(`Could not get Raydium amm pools from Raydium API`);
}
const raydiumPool = pools.filter((d) => d.id === position.poolId.toString()).shift();
if (!raydiumPool) {
throw Error(`Could not get find Raydium amm pool ${strategy.pool} from Raydium API`);
}
const priceRange = (0, utils_1.getStrategyPriceRangeRaydium)(position.tickLowerIndex, position.tickUpperIndex, Number(poolState.tickCurrent.toString()), Number(strategy.tokenAMintDecimals.toString()), Number(strategy.tokenBMintDecimals.toString()));
if (priceRange.strategyOutOfRange) {
return {
...priceRange,
rewardsApy: [],
rewardsApr: [],
feeApy: utils_1.ZERO,
feeApr: utils_1.ZERO,
totalApy: utils_1.ZERO,
totalApr: utils_1.ZERO,
};
}
const raydiumPoolInfo = await this.getRaydiumPoolInfo(strategy.pool);
console.log('raydiumPoolInfo', raydiumPoolInfo);
const params = {
poolInfo: raydiumPoolInfo,
aprType: 'day',
positionTickLowerIndex: position.tickLowerIndex,
positionTickUpperIndex: position.tickUpperIndex,
};
const { apr, feeApr, rewardsApr } = lib_1.PoolUtils.estimateAprsForPriceRangeMultiplier(params);
const totalApr = new decimal_js_1.default(apr).div(100);
const fee = new decimal_js_1.default(feeApr).div(100);
const rewards = rewardsApr.map((reward) => new decimal_js_1.default(reward).div(100));
return {
totalApr,
totalApy: (0, utils_1.aprToApy)(totalApr, 365),
feeApr: fee,
feeApy: (0, utils_1.aprToApy)(fee, 365),
rewardsApr: rewards,
rewardsApy: rewards.map((x) => (0, utils_1.aprToApy)(x, 365)),
...priceRange,
};
};
getRaydiumPositionAprApy = async (poolPubkey, priceLower, priceUpper, pools) => {
const poolState = await accounts_1.PoolState.fetch(this._rpc, poolPubkey);
if (!poolState) {
throw Error(`Raydium pool state ${poolPubkey} does not exist`);
}
if (!pools) {
({ data: pools } = await this.getRaydiumWhirlpools());
}
if (!pools || pools.length === 0) {
throw Error(`Could not get Raydium amm pools from Raydium API`);
}
const raydiumPool = pools.filter((d) => d.id === poolPubkey.toString()).shift();
if (!raydiumPool) {
throw Error(`Could not get find Raydium amm pool ${poolPubkey.toString()} from Raydium API`);
}
const tickLowerIndex = lib_1.TickMath.getTickWithPriceAndTickspacing(priceLower, poolState.tickSpacing, poolState.mintDecimals0, poolState.mintDecimals1);
const tickUpperIndex = lib_1.TickMath.getTickWithPriceAndTickspacing(priceUpper, poolState.tickSpacing, poolState.mintDecimals0, poolState.mintDecimals1);
const priceRange = (0, utils_1.getStrategyPriceRangeRaydium)(tickLowerIndex, tickUpperIndex, Number(poolState.tickCurrent.toString()), raydiumPool.mintDecimalsA, raydiumPool.mintDecimalsB);
if (priceRange.strategyOutOfRange) {
return {
...priceRange,
rewardsApy: [],
rewardsApr: [],
feeApy: utils_1.ZERO,
feeApr: utils_1.ZERO,
totalApy: utils_1.ZERO,
totalApr: utils_1.ZERO,
};
}
const poolInfo = await this.getRaydiumPoolInfo(poolPubkey);
const params = {
poolInfo,
aprType: 'day',
positionTickLowerIndex: tickLowerIndex,
positionTickUpperIndex: tickUpperIndex,
};
const { apr, feeApr, rewardsApr } = lib_1.PoolUtils.estimateAprsForPriceRangeMultiplier(params);
const totalApr = new decimal_js_1.default(apr).div(100);
const fee = new decimal_js_1.default(feeApr).div(100);
const rewards = rewardsApr.map((reward) => new decimal_js_1.default(reward).div(100));
return {
totalApr,
totalApy: (0, utils_1.aprToApy)(totalApr, 365),
feeApr: fee,
feeApy: (0, utils_1.aprToApy)(fee, 365),
rewardsApr: rewards,
rewardsApy: rewards.map((x) => (0, utils_1.aprToApy)(x, 365)),
...priceRange,
};
};
async getGenericPoolInfo(poolPubkey, pools) {
const poolState = await accounts_1.PoolState.fetch(this._rpc, poolPubkey);
if (!poolState) {
throw Error(`Raydium pool state ${poolPubkey} does not exist`);
}
if (!pools) {
({ data: pools } = await this.getRaydiumWhirlpools());
}
if (!pools || pools.length === 0) {
throw Error(`Could not get Raydium amm pools from Raydium API`);
}
const raydiumPool = pools.filter((d) => d.id === poolPubkey.toString()).shift();
if (!raydiumPool) {
throw Error(`Could not get find Raydium amm pool ${poolPubkey.toString()} from Raydium API`);
}
const poolInfo = {
dex: 'RAYDIUM',
address: poolPubkey,
tokenMintA: poolState.tokenMint0,
tokenMintB: poolState.tokenMint1,
price: new decimal_js_1.default(raydiumPool.price),
feeRate: new decimal_js_1.default(raydiumPool.ammConfig.tradeFeeRate).div(new decimal_js_1.default(CreationParameters_1.FullPercentage)),
volumeOnLast7d: new decimal_js_1.default(raydiumPool.week.volume),
tvl: new decimal_js_1.default(raydiumPool.tvl),
tickSpacing: new decimal_js_1.default(raydiumPool.ammConfig.tickSpacing),
// todo(Silviu): get real amount of positions
positions: new decimal_js_1.default(0),
};
return poolInfo;
}
async getPositionsCountByPool(pool) {
const positions = await this._rpc
.getProgramAccounts(this._raydiumProgramId, {
commitment: 'confirmed',
filters: [
{ dataSize: BigInt(lib_1.PositionInfoLayout.span) },
{
memcmp: {
bytes: pool.toString(),
offset: BigInt(lib_1.PositionInfoLayout.offsetOf('poolId')),
encoding: 'base58',
},
},
],
})
.send();
return positions.length;
}
getTickPrice(tick, tokenADecimals, tokenBDecimals, baseIn = true) {
const tickSqrtPriceX64 = lib_1.SqrtPriceMath.getSqrtPriceX64FromTick(tick);
const tickPrice = lib_1.SqrtPriceMath.sqrtPriceX64ToPrice(tickSqrtPriceX64, tokenADecimals, tokenBDecimals);
return baseIn
? { tick, price: tickPrice, tickSqrtPriceX64 }
: { tick, price: new decimal_js_1.default(1).div(tickPrice), tickSqrtPriceX64 };
}
async getRpcClmmPoolInfo(poolId) {
const poolAccountInfo = await this._rpc
.getAccountInfo((0, kit_1.address)(poolId), { commitment: 'confirmed', encoding: 'base64' })
.send();
if (!poolAccountInfo.value) {
throw Error(`Raydium pool state ${poolId} does not exist`);
}
const poolState = await accounts_1.PoolState.fetch(this._rpc, (0, kit_1.address)(poolId));
if (!poolState) {
throw Error(`Raydium pool state ${poolId} does not exist`);
}
const rpc = lib_1.PoolInfoLayout.decode(Buffer.from(poolAccountInfo.value.data[0], 'base64'));
const currentPrice = lib_1.SqrtPriceMath.sqrtPriceX64ToPrice(poolState.sqrtPriceX64, poolState.mintDecimals0, poolState.mintDecimals1).toNumber();
const data = {
...rpc,
currentPrice,
programId: (0, utils_1.toLegacyPublicKey)(this._raydiumProgramId),
};
return data;
}
async fetchComputeClmmInfo(poolInfoWithAddress) {
const { address: poolAddress } = poolInfoWithAddress;
return (await this.fetchComputeMultipleClmmInfo([poolInfoWithAddress]))[poolAddress.toString()];
}
async fetchExBitmaps(exBitmapAddress) {
const fetchedBitmapAccount = await this._rpc
.getMultipleAccounts(exBitmapAddress, { commitment: 'confirmed', encoding: 'base64' })
.send();
const fetchedBitmapAccountWithAddress = exBitmapAddress.map((address, index) => ({
address,
data: fetchedBitmapAccount.value[index]?.data,
}));
const returnTypeFetchExBitmaps = {};
for (const { address, data } of fetchedBitmapAccountWithAddress) {
if (!data)
continue;
returnTypeFetchExBitmaps[address.toString()] = lib_1.TickArrayBitmapExtensionLayout.decode(Buffer.from(data[0], 'base64'));
}
return returnTypeFetchExBitmaps;
}
async fetchComputeMultipleClmmInfo(poolList) {
const fetchRpcList = poolList.map((p) => p.address);
const rpcRes = await this._rpc
.getMultipleAccounts(fetchRpcList, { commitment: 'confirmed', encoding: 'base64' })
.send();
const rpcResWithAddress = fetchRpcList.map((address, index) => ({
address,
data: rpcRes.value[index]?.data,
}));
const rpcDataMap = {};
for (const { address, data } of rpcResWithAddress) {
if (!data)
continue;
rpcDataMap[address.toString()] = lib_1.PoolInfoLayout.decode(Buffer.from(data[0], 'base64'));
}
// Fetch amm configs
const configSet = Array.from(new Set(poolList.map((p) => p.clmmPoolRpcInfo.ammConfig.toBase58())));
const configRes = await this._rpc
.getMultipleAccounts(configSet.map((s) => (0, kit_1.address)(s)), { commitment: 'confirmed', encoding: 'base64' })
.send();
const clmmConfigs = {};
configSet.forEach((configAddress, index) => {
const data = configRes.value[index]?.data;
if (data) {
clmmConfigs[configAddress] = lib_1.ClmmConfigLayout.decode(Buffer.from(data[0], 'base64'));
}
});
// Fetch mint infos
const allMints = Array.from(new Set(poolList.flatMap((p) => [p.clmmPoolRpcInfo.mintA.toBase58(), p.clmmPoolRpcInfo.mintB.toBase58()])));
const mintInfos = await this.fetchMultipleMintInfos(allMints.map((m) => (0, kit_1.address)(m)));
const pdaList = await Promise.all(poolList.map(async (poolInfo) => {
const pda = await getPdaExBitmapAccount((0, compat_1.fromLegacyPublicKey)(poolInfo.clmmPoolRpcInfo.programId), poolInfo.address);
return (0, kit_1.address)(pda[0]);
}));
const exBitData = await this.fetchExBitmaps(pdaList);
const result = {};
for (let i = 0; i < poolList.length; i++) {
const cur = poolList[i];
const pda = await getPdaExBitmapAccount((0, compat_1.fromLegacyPublicKey)(cur.clmmPoolRpcInfo.programId), cur.address);
const ammConfigKey = cur.clmmPoolRpcInfo.ammConfig.toBase58();
const ammConfigData = clmmConfigs[ammConfigKey];
const mintAKey = cur.clmmPoolRpcInfo.mintA.toBase58();
const mintBKey = cur.clmmPoolRpcInfo.mintB.toBase58();
result[cur.address.toString()] = {
...rpcDataMap[cur.address.toString()],
id: (0, utils_1.toLegacyPublicKey)(cur.address),
version: 6,
programId: (0, compat_1.fromLegacyPublicKey)(cur.clmmPoolRpcInfo.programId),
mintA: {
chainId: 101,
address: mintAKey,
programId: mintInfos[mintAKey]?.programId.toBase58() || 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
logoURI: '',
symbol: '',
name: '',
decimals: cur.clmmPoolRpcInfo.mintDecimalsA,
tags: [],
extensions: { feeConfig: mintInfos[mintAKey]?.feeConfig },
},
mintB: {
chainId: 101,
address: mintBKey,
programId: mintInfos[mintBKey]?.programId.toBase58() || 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
logoURI: '',
symbol: '',
name: '',
decimals: cur.clmmPoolRpcInfo.mintDecimalsB,
tags: [],
extensions: { feeConfig: mintInfos[mintBKey]?.feeConfig },
},
ammConfig: {
...ammConfigData,
id: (0, utils_1.toLegacyPublicKey)((0, kit_1.address)(ammConfigKey)),
fundFeeRate: 0,
description: '',
defaultRange: 0,
defaultRangePoint: [],
fundOwner: '',
},
currentPrice: new decimal_js_1.default(cur.clmmPoolRpcInfo.currentPrice),
exBitmapAccount: (0, utils_1.toLegacyPublicKey)(pda[0]),
exBitmapInfo: exBitData[pda[0].toString()],
startTime: rpcDataMap[cur.address.toString()].startTime.toNumber(),
rewardInfos: rpcDataMap[cur.address.toString()].rewardInfos,
};
}
return result;
}
async fetchMultipleMintInfos(mints) {
if (mints.length === 0)
return {};
const cleanedMints = mints.map((i) => (0, utils_1.solToWSol)(i));
// fetch in chunks of 100
const mintInfos = [];
for (let i = 0; i < cleanedMints.length; i += 100) {
const chunk = cleanedMints.slice(i, i + 100);
const chunkMintInfos = await (0, token_2022_1.fetchAllMint)(this._rpc, chunk);
mintInfos.push(...chunkMintInfos);
}
const mintInfosWithAddress = cleanedMints.map((mint, index) => ({
address: mint,
data: mintInfos[index],
}));
const mintK = {};
for (const { address, data } of mintInfosWithAddress) {
if (!data) {
console.log('invalid mint account', address.toString());
continue;
}
mintK[address] = {
mintAuthority: (0, utils_1.optionGetValueOrUndefined)(data.data.mintAuthority)
? (0, utils_1.toLegacyPublicKey)((0, utils_1.optionGetValue)(data.data.mintAuthority))
: null,
programId: (0, utils_1.toLegacyPublicKey)(data.programAddress),
feeConfig: undefined,
address: (0, utils_1.toLegacyPublicKey)(address),
isInitialized: true,
tlvData: Buffer.from([]),
supply: data.data.supply,
decimals: data.data.decimals,
freezeAuthority: (0, utils_1.optionGetValueOrUndefined)(data.data.freezeAuthority)
? (0, utils_1.toLegacyPublicKey)((0, utils_1.optionGetValue)(data.data.freezeAuthority))
: null,
};
}
mintK[pubkeys_1.DEFAULT_PUBLIC_KEY.toString()] = mintK[utils_1.WSOL_MINT.toString()];
return mintK;
}
async getComputeClmmPoolInfo(clmmPoolRpcInfoWithAddress) {
const computeClmmPoolInfo = await this.fetchComputeClmmInfo(clmmPoolRpcInfoWithAddress);
return computeClmmPoolInfo;
}
async getPoolInfoFromRpc(poolId) {
const rpcData = await this.getRpcClmmPoolInfo(poolId);
const poolInfoWithAddress = {
address: (0, kit_1.address)(poolId),
clmmPoolRpcInfo: rpcData,
};
const computeClmmPoolInfo = await this.getComputeClmmPoolInfo(poolInfoWithAddress);
const poolInfo = this.clmmComputeInfoToApiInfo(computeClmmPoolInfo);
poolInfo.mintAmountA = await (0, utils_1.getTokenAccountBalanceLamports)(this._rpc, (0, compat_1.fromLegacyPublicKey)(rpcData.vaultA));
poolInfo.mintAmountB = await (0, utils_1.getTokenAccountBalanceLamports)(this._rpc, (0, compat_1.fromLegacyPublicKey)(rpcData.vaultB));
return poolInfo;
}
clmmComputeInfoToApiInfo(pool) {
return {
...pool,
type: 'Concentrated',
programId: pool.programId.toString(),
id: pool.id.toString(),
rewardDefaultInfos: [],
rewardDefaultPoolInfos: 'Clmm',
price: pool.currentPrice.toNumber(),
mintAmountA: 0,
mintAmountB: 0,
feeRate: pool.ammConfig.tradeFeeRate,
openTime: pool.startTime.toString(),
tvl: 0,
day: mockRewardData,
week: mockRewardData,
month: mockRewardData,
pooltype: [],
farmUpcomingCount: 0,
farmOngoingCount: 0,
farmFinishedCount: 0,
burnPercent: 0,
config: {
...pool.ammConfig,
id: pool.ammConfig.id.toString(),
defaultRange: 0,
defaultRangePoint: [],
},
};
}
}
exports.RaydiumService = RaydiumService;
async function getPdaExBitmapAccount(programId, poolId) {
const addressEncoder = (0, kit_1.getAddressEncoder)();
return (0, kit_1.getProgramDerivedAddress)({
seeds: [lib_1.POOL_TICK_ARRAY_BITMAP_SEED, addressEncoder.encode(poolId)],
programAddress: programId,
});
}
const mockRewardData = {
volume: 0,
volumeQuote: 0,
volumeFee: 0,
apr: 0,
feeApr: 0,
priceMin: 0,
priceMax: 0,
rewardApr: [],
};
//# sourceMappingURL=RaydiumService.js.map