UNPKG

@kamino-finance/farms-sdk

Version:
172 lines (150 loc) 5 kB
import { UserState } from "../@codegen/farms/accounts/userState"; import { FarmState } from "../@codegen/farms/accounts/farmState"; import Decimal from "decimal.js"; import { WAD } from "./utils"; import { RewardInfo, RewardType } from "../@codegen/farms/types"; import { OraclePrices } from "@kamino-finance/scope-sdk/dist/@codegen/scope/accounts"; import { DEFAULT_PUBLIC_KEY } from "./pubkey"; export function calculatePendingRewards( farmState: FarmState, userState: UserState, rewardIndex: number, ts: Decimal, scopePrices: OraclePrices | null, ): Decimal { let scopePrice: Decimal | null = scopePriceForFarm(farmState, scopePrices); const newRewardPerToken = calculateRewardPerStake( farmState, ts, rewardIndex, scopePrice, ); const rewardTally = new Decimal( userState.rewardsTallyScaled[rewardIndex].toString(), ).div(WAD); let activeStakeScaled = new Decimal(0); if (farmState.delegateAuthority === DEFAULT_PUBLIC_KEY) { activeStakeScaled = new Decimal(userState.activeStakeScaled.toString()).div( WAD, ); } else { activeStakeScaled = new Decimal(userState.activeStakeScaled.toString()); } const newRewardTally = activeStakeScaled.mul(newRewardPerToken); const newReward = new Decimal(newRewardTally.sub(rewardTally).toFixed(0)); const prevReward = new Decimal( userState.rewardsIssuedUnclaimed[rewardIndex].toString(), ); const finalReward = prevReward.add(newReward); return finalReward; } export function calculateCurrentRewardPerToken( rewardInfo: RewardInfo, currentTimeUnit: Decimal, ): number { const rewardCurve = rewardInfo.rewardScheduleCurve; let index = 0; for (let i = 0; i < rewardCurve.points.length; i++) { if ( new Decimal(rewardCurve.points[i].tsStart.toString()).lte(currentTimeUnit) ) { index = i; } else { break; } } return Number(rewardCurve.points[index].rewardPerTimeUnit); } function calculateRewardPerStake( farmState: FarmState, ts: Decimal, rewardIndex: number, scopePrice: Decimal | null, ): Decimal { const rewardInfo = farmState.rewardInfos[rewardIndex]; const newRewards = calculateNewRewardToBeIssued( farmState, ts, rewardIndex, scopePrice, ); let scaledRewards = newRewards.mul(WAD); let rewardPerTokenScaled = new Decimal( rewardInfo.rewardPerShareScaled.toString(), ).div(WAD); let rewardPerTokenScaledAdded = new Decimal(0); const totalActiveStakeScaled = new Decimal( farmState.totalActiveStakeScaled.toString(), ); if (farmState.delegateAuthority === DEFAULT_PUBLIC_KEY) { rewardPerTokenScaledAdded = scaledRewards.div(totalActiveStakeScaled); } else { if ( scaledRewards.gt(new Decimal(0)) && totalActiveStakeScaled.gt(new Decimal(0)) ) { rewardPerTokenScaledAdded = scaledRewards .div(totalActiveStakeScaled) .div(WAD); } } const finalRewardPerToken = rewardPerTokenScaled.add( rewardPerTokenScaledAdded, ); return finalRewardPerToken; } export function calculateNewRewardToBeIssued( farmState: FarmState, ts: Decimal, rewardIndex: number, scopePrice: Decimal | null, ): Decimal { const rewardInfo = farmState.rewardInfos[rewardIndex]; const tsDiff = ts.sub(new Decimal(rewardInfo.lastIssuanceTs.toString())); let rps = calculateCurrentRewardPerToken(rewardInfo, ts); let rpsDecimal = new Decimal(10 ** rewardInfo.rewardsPerSecondDecimals); let newRewards = tsDiff.mul(new Decimal(rps)).div(rpsDecimal); switch (rewardInfo.rewardType) { case RewardType.Proportional: // `rps` means `reward per second for entire farm` break; case RewardType.Constant: { // `rps` means `reward per second for each lamport staked` const totalStaked = new Decimal(farmState.totalStakedAmount.toString()); newRewards = newRewards.mul(totalStaked); break; } } if (farmState.scopePrices !== DEFAULT_PUBLIC_KEY) { // Oracle adjustment if (scopePrice == null) { throw new Error("Scope price not provided"); } console.log("Adjusting by scope price", scopePrice.toString()); newRewards = newRewards.mul(scopePrice); } // We cap rewards by how much is available in the farm anyway let cappedRewards = Decimal.min( newRewards, new Decimal(rewardInfo.rewardsAvailable.toString()), ); return cappedRewards; } export function scopePriceForFarm( farmState: FarmState, scopePrices: OraclePrices | null, ): Decimal | null { let scopePrice: Decimal | null = null; if (farmState.scopePrices !== DEFAULT_PUBLIC_KEY) { if (scopePrices == null) { throw new Error("Scope prices not provided"); } const price = scopePrices.prices[ new Decimal(farmState.scopeOraclePriceId.toString()).toNumber() ]; const factor = new Decimal(10).pow(price.price.exp.toString()); scopePrice = new Decimal(price.price.value.toString()).div(factor); } return scopePrice; }