UNPKG

@kamino-finance/farms-sdk

Version:
196 lines 11.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSeasonFarmsFromCDN = getSeasonFarmsFromCDN; exports.getExistingUserStatesMapForFarm = getExistingUserStatesMapForFarm; exports.getUserStateDefaultLastClaimTs = getUserStateDefaultLastClaimTs; exports.getRewardIndexInFarm = getRewardIndexInFarm; exports.getSeasonFarmsData = getSeasonFarmsData; const exponential_backoff_1 = require("exponential-backoff"); const axios_1 = __importDefault(require("axios")); const vestingUtils_1 = require("./vestingUtils"); const accounts_1 = require("../@codegen/farms/accounts"); const kit_1 = require("@solana/kit"); const decimal_js_1 = __importDefault(require("decimal.js")); const utils_1 = require("./utils"); const pubkey_1 = require("./pubkey"); async function getSeasonFarmsFromCDN() { return (await (0, exponential_backoff_1.backOff)(() => axios_1.default.get("https://cdn.kamino.finance/resources.json"), { maxDelay: 1000, numOfAttempts: 5, startingDelay: 10, })).data["mainnet-beta"].seasonFarms; } async function getExistingUserStatesMapForFarm(farmsClient, farm) { const userStates = await farmsClient.getAllUserStatesForFarm(farm); const userStatesMap = new Map(); for (const { key, userState } of userStates) { userStatesMap.set(key, userState); } return userStatesMap; } function getUserStateDefaultLastClaimTs(farmState, userState) { const firstEmptyRewardIndex = farmState.rewardInfos.findIndex((rewardInfo) => { return rewardInfo.token.mint === pubkey_1.DEFAULT_PUBLIC_KEY; }); if (firstEmptyRewardIndex === -1) { throw new Error("No empty reward slot found in farm state"); } return Number(userState.lastClaimTs[firstEmptyRewardIndex]); } function getRewardIndexInFarm(farmState, rewardMint) { return farmState.rewardInfos.findIndex((rewardInfo) => { return rewardInfo.token.mint === rewardMint; }); } async function getSeasonFarmsData(farmsClient, userAllocations) { const seasonFarms = await getSeasonFarmsFromCDN(); const totalUsersAllocations = userAllocations.reduce((acc, ua) => acc + ua.allocationLamports, 0); const statsTimestampSeconds = Date.now() / 1000; const seasonFarmsData = []; for (const seasonFarm of seasonFarms) { const seasonFarmAddress = (0, kit_1.address)(seasonFarm.farmAddress); const farmAccount = await (0, accounts_1.fetchMaybeFarmState)(farmsClient.getConnection(), seasonFarmAddress); if (!farmAccount.exists) { throw new Error(`Farm not found: ${seasonFarm.farmAddress}`); } const farmState = farmAccount.data; const rewardIndex = getRewardIndexInFarm(farmState, seasonFarm.rewardMint); if (rewardIndex === -1) { console.warn(`Skipping farm ${seasonFarm.farmAddress}: Reward mint ${seasonFarm.rewardMint} not found in farm reward infos`); continue; } const rewardDecimals = Number(farmState.rewardInfos[rewardIndex].token.decimals); const rewardDecimalFactor = new decimal_js_1.default(10).pow(rewardDecimals); const totalFarmVestingCalculation = (0, vestingUtils_1.calculateVestingAtTime)(new decimal_js_1.default(totalUsersAllocations), statsTimestampSeconds, seasonFarm.vestingConfig); const farmRewardVaultBalanceLamports = await (0, utils_1.getTokenAccountBalanceLamports)(farmsClient.getConnection(), farmState.rewardInfos[rewardIndex].rewardsVault); const farmRewardVaultBalance = new decimal_js_1.default(farmRewardVaultBalanceLamports).div(rewardDecimalFactor); const farmRewardsIssuedCumulative = new decimal_js_1.default(farmState.rewardInfos[rewardIndex].rewardsIssuedCumulative.toString()).div(rewardDecimalFactor); const farmRewardsIssuedUnclaimed = new decimal_js_1.default(farmState.rewardInfos[rewardIndex].rewardsIssuedUnclaimed.toString()).div(rewardDecimalFactor); // this will not decrease from claims - this only increases when rewards are issued via the refresh rewards process, which will do nothing for season farms, hence rewardsAvailable will show us how much was topped up in total to the farm const rewardsAvailable = new decimal_js_1.default(farmState.rewardInfos[rewardIndex].rewardsAvailable.toString()).div(rewardDecimalFactor); const rewardsClaimedFromVault = rewardsAvailable.minus(farmRewardVaultBalance); const rewardsClaimedFromFarmAccounting = farmRewardsIssuedCumulative.minus(farmRewardsIssuedUnclaimed); const doRewardsClaimedMatch = rewardsClaimedFromVault.equals(rewardsClaimedFromFarmAccounting); const totalClaimableAllocation = totalFarmVestingCalculation.claimableAmount.div(rewardDecimalFactor); const isTotalIssuedCummulativeLessOrThanClaimable = farmRewardsIssuedCumulative.lessThanOrEqualTo(totalClaimableAllocation); const isTotalIssuedUnclaimedLessOrThanClaimable = farmRewardsIssuedUnclaimed.lessThanOrEqualTo(totalClaimableAllocation); const pctRewardsAvailableInVaultToUnclaimed = farmRewardsIssuedUnclaimed.greaterThan(0) ? farmRewardVaultBalance.div(farmRewardsIssuedUnclaimed).mul(100) : new decimal_js_1.default(0); const userStates = await getExistingUserStatesMapForFarm(farmsClient, seasonFarmAddress); const vestingEndTsSeconds = seasonFarm.vestingConfig.vestingStartTimestampSeconds + seasonFarm.vestingConfig.vestingDurationSeconds; const hasVestingStarted = statsTimestampSeconds >= seasonFarm.vestingConfig.vestingStartTimestampSeconds; const hasVestingEnded = statsTimestampSeconds >= vestingEndTsSeconds; const userStats = []; let numberOfUserStatesUninitialized = 0; let numberOfUsersClaimed = 0; let numberOfFullyVestedUsers = 0; let numberOfUsersForfited = 0; let numberOfUsersWithUnexpectedUnclaimedAmounts = 0; let numberOfUsersWithUnexpectedCumulativeAmounts = 0; let totalForfeitures = new decimal_js_1.default(0); for (const ua of userAllocations) { let isUserFullyVested = false; let isUserInitialized = true; let hasUserClaimed = false; let hasUserForfited = false; let isUserRewardsIssuedUnclaimedExpected = false; let isUserRewardsIssuedCumulativeExpected = false; const userStatePda = await (0, utils_1.getUserStatePDA)(farmsClient.getProgramID(), seasonFarmAddress, ua.address); const userState = userStates.get(userStatePda); if (!userState) { numberOfUserStatesUninitialized += 1; isUserInitialized = false; continue; } const userAllocationDecimal = new decimal_js_1.default(ua.allocationLamports).div(rewardDecimalFactor); const userAllocationVestingCalculation = (0, vestingUtils_1.calculateVestingAtTime)(new decimal_js_1.default(ua.allocationLamports), statsTimestampSeconds, seasonFarm.vestingConfig); const userTotalClaimableAtStatsTs = userAllocationVestingCalculation.claimableAmount.div(rewardDecimalFactor); const userDefaultLastClaimTs = getUserStateDefaultLastClaimTs(farmState, userState); if (Number(userState.lastClaimTs[rewardIndex]) > userDefaultLastClaimTs) { const userTotalClaimedDecimal = new decimal_js_1.default(userState.rewardsIssuedCumulative[rewardIndex].toString()).div(rewardDecimalFactor); const userForfeitureDecimal = userAllocationDecimal.sub(userTotalClaimedDecimal); totalForfeitures = totalForfeitures.plus(userForfeitureDecimal); if (userForfeitureDecimal.greaterThan(0)) { hasUserForfited = true; numberOfUsersForfited += 1; } numberOfUsersClaimed += 1; hasUserClaimed = true; const userRewardsIssuedUnclaimed = new decimal_js_1.default(userState.rewardsIssuedUnclaimed[rewardIndex].toString()).div(rewardDecimalFactor); isUserRewardsIssuedUnclaimedExpected = userRewardsIssuedUnclaimed.equals(0); } else { const userRewardsIssuedUnclaimed = new decimal_js_1.default(userState.rewardsIssuedUnclaimed[rewardIndex].toString()).div(rewardDecimalFactor); isUserRewardsIssuedUnclaimedExpected = userRewardsIssuedUnclaimed.lessThanOrEqualTo(userTotalClaimableAtStatsTs); } isUserRewardsIssuedCumulativeExpected = new decimal_js_1.default(userState.rewardsIssuedCumulative[rewardIndex].toString()) .div(rewardDecimalFactor) .lessThanOrEqualTo(userTotalClaimableAtStatsTs); const hasClaimedAfterVestingEnd = Number(userState.lastClaimTs[rewardIndex]) > vestingEndTsSeconds; const userRewardsIssuedUnclaimed = new decimal_js_1.default(userState.rewardsIssuedUnclaimed[rewardIndex].toString()).div(rewardDecimalFactor); const isFullyVestedAndAwarded = userTotalClaimableAtStatsTs.equals(userRewardsIssuedUnclaimed) && userTotalClaimableAtStatsTs.equals(userAllocationDecimal) && statsTimestampSeconds > vestingEndTsSeconds && Number(userState.lastClaimTs[rewardIndex]) === userDefaultLastClaimTs; if (isFullyVestedAndAwarded || hasClaimedAfterVestingEnd) { numberOfFullyVestedUsers += 1; isUserFullyVested = true; } numberOfUsersWithUnexpectedUnclaimedAmounts += isUserRewardsIssuedUnclaimedExpected ? 0 : 1; numberOfUsersWithUnexpectedCumulativeAmounts += isUserRewardsIssuedCumulativeExpected ? 0 : 1; userStats.push({ userAddress: ua.address, userAllocation: userAllocationDecimal, isUserInitialized, hasUserClaimed, hasUserForfited, isUserFullyVested, userTotalClaimableAtStatsTs, isUserRewardsIssuedUnclaimedExpected, isUserRewardsIssuedCumulativeExpected, }); } seasonFarmsData.push({ seasonFarm: seasonFarm, farmState, userStates, stats: { statsTimestampSeconds, hasVestingStarted, hasVestingEnded, farmRewardVaultBalance, rewardsClaimedFromVault, rewardsClaimedFromFarmAccounting, totalClaimableAllocation, farmRewardsIssuedCumulative, farmRewardsIssuedUnclaimed, pctRewardsAvailableInVaultToUnclaimed, doRewardsClaimedMatch, isTotalIssuedCummulativeLessOrThanClaimable, isTotalIssuedUnclaimedLessOrThanClaimable, totalUsers: userAllocations.length, numberOfFullyVestedUsers, numberOfUserStatesUninitialized, numberOfUsersWithUnexpectedUnclaimedAmounts, numberOfUsersForfited, numberOfUsersClaimed, numberOfUsersWithUnexpectedCumulativeAmounts, totalForfeitures, userStats, totalFarmVestingCalculation, }, }); } return seasonFarmsData; } //# sourceMappingURL=seasonFarmUtils.js.map