UNPKG

test-raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

338 lines (301 loc) 11.6 kB
import { Connection, PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { GetMultipleAccountsInfoConfig, getMultipleAccountsInfoWithCustomFlags } from "@/common/accountInfo"; import { parseBigNumberish } from "@/common/bignumber"; import { createLogger } from "@/common/logger"; import { findProgramAddress, ProgramAddress } from "@/common/txTool/txUtils"; import { DateParam, isDateAfter, isDateBefore } from "@/common/date"; import { jsonInfo2PoolKeys } from "@/common/utility"; import { RewardInfoV6 } from "@/api/type"; import { splAccountLayout } from "../account/layout"; import { SplAccount } from "../account/types"; import { FARM_VERSION_TO_LEDGER_LAYOUT, FARM_VERSION_TO_STATE_LAYOUT, poolTypeV6 } from "./config"; import { FarmLedger, FarmLedgerLayout, FarmState, FarmStateLayout } from "./layout"; import { FarmRewardInfo, FarmRewardInfoConfig } from "./type"; import { VoterRegistrar, Voter } from "./layout"; const logger = createLogger("Raydium.farm.util"); interface AssociatedLedgerPoolAccount { programId: PublicKey; poolId: PublicKey; mint: PublicKey; type: "lpVault" | "rewardVault"; } export function getAssociatedLedgerPoolAccount({ programId, poolId, mint, type, }: AssociatedLedgerPoolAccount): PublicKey { const { publicKey } = findProgramAddress( [ poolId.toBuffer(), mint.toBuffer(), Buffer.from( type === "lpVault" ? "lp_vault_associated_seed" : type === "rewardVault" ? "reward_vault_associated_seed" : "", "utf-8", ), ], programId, ); return publicKey; } export function getAssociatedLedgerAccount({ programId, poolId, owner, version, }: { programId: PublicKey; poolId: PublicKey; owner: PublicKey; version: 6 | 5 | 3; }): PublicKey { const { publicKey } = findProgramAddress( [ poolId.toBuffer(), owner.toBuffer(), Buffer.from(version === 6 ? "farmer_info_associated_seed" : "staker_info_v2_associated_seed", "utf-8"), ], programId, ); return publicKey; } export const getAssociatedAuthority = ({ programId, poolId, }: { programId: PublicKey; poolId: PublicKey; }): ProgramAddress => findProgramAddress([poolId.toBuffer()], programId); export function farmRewardInfoToConfig(data: FarmRewardInfo): FarmRewardInfoConfig { return { isSet: new BN(1), rewardPerSecond: parseBigNumberish(data.perSecond), rewardOpenTime: parseBigNumberish(data.openTime), rewardEndTime: parseBigNumberish(data.endTime), rewardType: parseBigNumberish(poolTypeV6[data.rewardType]), }; } export function calFarmRewardAmount(data: Pick<RewardInfoV6, "openTime" | "endTime"> & { perSecond: string }): BN { return parseBigNumberish(data.endTime).sub(parseBigNumberish(data.openTime)).mul(parseBigNumberish(data.perSecond)); } export function getFarmLedgerLayout(version: number): FarmLedgerLayout | undefined { const ledgerLayout = FARM_VERSION_TO_LEDGER_LAYOUT[version]; if (!ledgerLayout) logger.logWithError("invalid version", version); return ledgerLayout; } export function getFarmStateLayout(version: number): FarmStateLayout | undefined { const stateLayout = FARM_VERSION_TO_STATE_LAYOUT[version]; if (!stateLayout) logger.logWithError("invalid version", version); return stateLayout; } export function updateFarmPoolInfo( poolInfo: FarmState, lpVault: SplAccount, slot: number, chainTime: number, ): FarmState { if (poolInfo.version === 3 || poolInfo.version === 5) { if (poolInfo.lastSlot.gte(new BN(slot))) return poolInfo; const spread = new BN(slot).sub(poolInfo.lastSlot); poolInfo.lastSlot = new BN(slot); for (const itemRewardInfo of poolInfo.rewardInfos) { if (lpVault.amount.eq(new BN(0))) continue; const reward = itemRewardInfo.perSlotReward.mul(spread); itemRewardInfo.perShareReward = itemRewardInfo.perShareReward.add( reward.mul(new BN(10).pow(new BN(poolInfo.version === 3 ? 9 : 15))).div(lpVault.amount), ); itemRewardInfo.totalReward = itemRewardInfo.totalReward.add(reward); } } else if (poolInfo.version === 6) { for (const itemRewardInfo of poolInfo.rewardInfos) { if (itemRewardInfo.rewardState.eq(new BN(0))) continue; const updateTime = BN.min(new BN(chainTime), itemRewardInfo.rewardEndTime); if (itemRewardInfo.rewardOpenTime.gte(updateTime)) continue; const spread = updateTime.sub(itemRewardInfo.rewardLastUpdateTime); let reward = spread.mul(itemRewardInfo.rewardPerSecond); const leftReward = itemRewardInfo.totalReward.sub(itemRewardInfo.totalRewardEmissioned); if (leftReward.lt(reward)) { reward = leftReward; itemRewardInfo.rewardLastUpdateTime = itemRewardInfo.rewardLastUpdateTime.add( leftReward.div(itemRewardInfo.rewardPerSecond), ); } else { itemRewardInfo.rewardLastUpdateTime = updateTime; } if (lpVault.amount.eq(new BN(0))) continue; itemRewardInfo.accRewardPerShare = itemRewardInfo.accRewardPerShare.add( reward.mul(poolInfo.rewardMultiplier).div(lpVault.amount), ); itemRewardInfo.totalRewardEmissioned = itemRewardInfo.totalRewardEmissioned.add(reward); } } return poolInfo; } interface FarmPoolsInfo { [id: string]: { state: FarmState; lpVault: SplAccount; ledger?: FarmLedger; wrapped?: { pendingRewards: BN[] }; }; } export interface FarmFetchMultipleInfoParams { connection: Connection; farmPools: any[]; owner?: PublicKey; config?: GetMultipleAccountsInfoConfig; chainTime: number; } export async function fetchMultipleFarmInfoAndUpdate({ connection, farmPools, owner, config, chainTime, }: FarmFetchMultipleInfoParams): Promise<FarmPoolsInfo> { let hasNotV6Pool = false; let hasV6Pool = false; const tenBN = new BN(10); const publicKeys: { pubkey: PublicKey; version: number; key: "state" | "lpVault" | "ledger"; poolId: PublicKey; }[] = []; for (const poolInfo of farmPools) { const pool = jsonInfo2PoolKeys(poolInfo); if (pool.version === 6) hasV6Pool = true; else hasNotV6Pool = true; publicKeys.push( { pubkey: pool.id, version: pool.version, key: "state", poolId: pool.id, }, { pubkey: pool.lpVault, version: pool.version, key: "lpVault", poolId: pool.id, }, ); if (owner) { publicKeys.push({ pubkey: getAssociatedLedgerAccount({ programId: pool.programId, poolId: pool.id, owner, version: poolInfo.version as 6 | 5 | 3, }), version: pool.version, key: "ledger", poolId: pool.id, }); } } const poolsInfo: FarmPoolsInfo = {}; const accountsInfo = await getMultipleAccountsInfoWithCustomFlags(connection, publicKeys, config); for (const { pubkey, version, key, poolId, accountInfo } of accountsInfo) { const _poolId = poolId.toBase58(); poolsInfo[_poolId] = { ...poolsInfo[_poolId] }; if (key === "state") { const stateLayout = getFarmStateLayout(version); if (!accountInfo || !accountInfo.data || accountInfo.data.length !== stateLayout!.span) logger.logWithError(`invalid farm state account info, pools.id, ${pubkey}`); poolsInfo[_poolId].state = stateLayout!.decode(accountInfo!.data); } else if (key === "lpVault") { if (!accountInfo || !accountInfo.data || accountInfo.data.length !== splAccountLayout.span) logger.logWithError(`invalid farm lp vault account info, pools.lpVault, ${pubkey}`); poolsInfo[_poolId].lpVault = splAccountLayout.decode(accountInfo!.data); } else if (key === "ledger") { const legerLayout = getFarmLedgerLayout(version)!; if (accountInfo && accountInfo.data) { if (accountInfo.data.length !== legerLayout.span) logger.logWithError(`invalid farm ledger account info, ledger, ${pubkey}`); poolsInfo[_poolId].ledger = legerLayout.decode(accountInfo.data); } } } const slot = hasV6Pool || hasNotV6Pool ? await connection.getSlot() : 0; for (const poolId of Object.keys(poolsInfo)) { if (poolsInfo[poolId] === undefined) continue; poolsInfo[poolId].state = updateFarmPoolInfo(poolsInfo[poolId].state, poolsInfo[poolId].lpVault, slot, chainTime); } for (const [poolId, { state, ledger }] of Object.entries(poolsInfo)) { if (ledger) { const multiplier = state.version === 6 ? state.rewardMultiplier : state.rewardInfos.length === 1 ? tenBN.pow(new BN(9)) : tenBN.pow(new BN(15)); const pendingRewards = state.rewardInfos.map((rewardInfo, index) => { const rewardDebt = ledger.rewardDebts[index]; const pendingReward = ledger.deposited .mul(state.version === 6 ? rewardInfo.accRewardPerShare : rewardInfo.perShareReward) .div(multiplier) .sub(rewardDebt); return pendingReward; }); poolsInfo[poolId].wrapped = { ...poolsInfo[poolId].wrapped, pendingRewards, }; } } return poolsInfo; } /** deprecated */ export function judgeFarmType( info: any, currentTime: DateParam = Date.now(), ): "closed pool" | "normal fusion pool" | "dual fusion pool" | undefined | "upcoming pool" { if (info.version === 6) { const rewardInfos = info.state.rewardInfos; if (rewardInfos.every(({ rewardOpenTime }) => isDateBefore(currentTime, rewardOpenTime.toNumber(), { unit: "s" }))) return "upcoming pool"; if (rewardInfos.every(({ rewardEndTime }) => isDateAfter(currentTime, rewardEndTime.toNumber(), { unit: "s" }))) return "closed pool"; } else { const perSlotRewards = info.state.rewardInfos.map(({ perSlotReward }) => perSlotReward); if (perSlotRewards.length === 2) { // v5 if (String(perSlotRewards[0]) === "0" && String(perSlotRewards[1]) !== "0") { return "normal fusion pool"; // reward xxx token } if (String(perSlotRewards[0]) !== "0" && String(perSlotRewards[1]) !== "0") { return "dual fusion pool"; // reward ray and xxx token } if (String(perSlotRewards[0]) === "0" && String(perSlotRewards[1]) === "0") { return "closed pool"; } } else if (perSlotRewards.length === 1) { // v3 if (String(perSlotRewards[0]) === "0") { return "closed pool"; } } } } export async function getDepositEntryIndex( connection: Connection, registrar: PublicKey, voter: PublicKey, voterMint: PublicKey, ): Promise<{ index: number; isInit: boolean }> { const registrarAccountData = await connection.getAccountInfo(registrar); if (registrarAccountData === null) throw Error("registrar info check error"); const registrarData = VoterRegistrar.decode(registrarAccountData.data); const votingMintConfigIndex = registrarData.votingMints.findIndex((i) => i.mint.equals(voterMint)); if (votingMintConfigIndex === -1) throw Error("find voter mint error"); const voterAccountData = await connection.getAccountInfo(voter); if (voterAccountData === null) return { index: votingMintConfigIndex, isInit: false }; // throw Error('voter info check error') const voterData = Voter.decode(voterAccountData.data); const depositEntryIndex = voterData.deposits.findIndex( (i) => i.isUsed && i.votingMintConfigIdx === votingMintConfigIndex, ); if (depositEntryIndex === -1) return { index: votingMintConfigIndex, isInit: false }; else return { index: depositEntryIndex, isInit: true }; }