Rewards_validator.js

import Reward from '../models/Reward.js'
import { setTimestampFormat } from '../utils/index.js'
import schedule from 'node-schedule'
import {
    getBlockTime,
    fetchLatestEpoch,
    getInflationReward,
    fetchSolanaPriceAtDate,
} from '../repository/network.repository.js'
import logger from '../logger/logger.js'
import dotenv from 'dotenv'

// Load environment variables from .env file
dotenv.config()

const LAMPORTS_PER_SOL = 1000000000

const VALIDATOR_PUB_KEY = process.env.VALIDATOR_PUB_KEY
const VALIDATOR_ID = process.env.VALIDATOR_ID
const START_EPOCH = parseInt(process.env.START_EPOCH)

/**
 * Initializes the function for rewards cron job.
 * @returns {Object} - A schedule job to run every day at 1am.
 */
const validatorRewardsCron = async () => {
    logger.info('Validator rewards cron started')

    // Schedule a daily job to run at 1am
    return schedule.scheduleJob('0 1 * * *', async () => {
        await validatorRewardsJob()
    })
}

/**
 * Job to populate validator rewards from the start epoch to the latest epoch
 */
const validatorRewardsJob = async () => {
    try {
        // Get the latest validator reward's epoch number
        const latestReward = await Reward.findOne({
            delegatorId: VALIDATOR_ID,
        }).sort({ epochNum: -1, timestamp: -1 })

        // Set initial epoch to the epoch where validator became active
        let currentEpoch = START_EPOCH
        if (latestReward) {
            currentEpoch = latestReward.epochNum + 1
        }
        // Get the latest epoch info
        const latestEpoch = await fetchLatestEpoch()

        // Loop through all epochs starting from the current one
        for (; currentEpoch <= latestEpoch; currentEpoch++) {
            if (latestEpoch === currentEpoch) {
                logger.info(`Reached latest Epoch: ${latestEpoch}`)
                break
            }
            // Fetch the validator's reward for the specific epoch
            const data = await getInflationReward(
                [VALIDATOR_PUB_KEY],
                currentEpoch
            )
            const rewards = data.result[0]
            if (rewards) {
                await processReward(rewards, currentEpoch)
            } else {
                logger.info('no rewards for epoch:', currentEpoch)
            }
        }
    } catch (e) {
        logger.error(`Validator rewards cron job failed: ${e.message}`)
    }
}

/**
 * Processes reward information and stores it in the database.
 * @param {Object} rewards - The reward object containing information about rewards.
 * @param {number} rewards.effectiveSlot - The effective slot number for the reward.
 * @param {number} rewards.amount - The amount of the reward in lamports.
 * @param {number} rewards.postBalance - The post balance in lamports after receiving the reward.
 * @param {number} rewards.epoch - The epoch number for the reward.
 * @param {number} epoch - The epoch number being processed.
 * @returns {Promise<void>} A promise that resolves once the reward has been processed.
 */
const processReward = async (rewards, epoch) => {
    const blockTime = await getBlockTime(rewards.effectiveSlot)
    const timestamp = setTimestampFormat(
        new Date(blockTime * 1000) // convert to milliseconds
    )
    const solUsd = await fetchSolanaPriceAtDate(timestamp)

    const { postBalance } = rewards
    const postBalanceUsd = (postBalance / LAMPORTS_PER_SOL) * solUsd

    const reward = rewards.amount
    const rewardUsd = (rewards.amount / LAMPORTS_PER_SOL) * solUsd

    let totalReward = rewards.amount
    let pendingRewards = rewards.amount

    // Get the previous reward
    const previousReward = await Reward.findOne({
        delegatorId: VALIDATOR_ID,
    })
        .sort({ timestamp: -1 })
        .exec()
    // If previous reward exists, add it to the current reward
    if (previousReward) {
        totalReward += previousReward.totalReward
        pendingRewards += previousReward.pendingRewards
    }
    const totalRewardUsd = (totalReward / LAMPORTS_PER_SOL) * solUsd
    const pendingRewardsUsd = (pendingRewards / LAMPORTS_PER_SOL) * solUsd

    await Reward.create({
        delegatorId: VALIDATOR_ID,
        epochNum: rewards.epoch,
        solUsd,
        timestamp,
        postBalance,
        postBalanceUsd,
        userAction: 'REWARD',
        reward,
        rewardUsd,
        totalReward,
        totalRewardUsd,
        pendingRewards,
        pendingRewardsUsd,
        stakedAmount: -1,
        stakedAmountUsd: -1,
    })

    logger.info(`processed reward for epoch [${epoch}]`)
}

export default validatorRewardsCron