repository_network.repository.js

import request from '../config/axiosInstance.js'
import { populateBody } from '../utils/index.js'
import logger from '../logger/logger.js'
import dotenv from 'dotenv'

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

const SOLANA_ENDPOINT = process.env.SOLANA_ENDPOINT
const EXCHANGE_URL = 'https://api.coingecko.com/api/v3'
const STAKE_PROGRAM_ID = 'Stake11111111111111111111111111111111111111'

/**
 * Fetch the current epoch number from the Solana network.
 * @returns {Promise<number|undefined>} The current epoch number, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const fetchLatestEpoch = async () => {
    try {
        const body = populateBody('getEpochInfo')
        const data = await request.POST(SOLANA_ENDPOINT, body)
        return data.result.epoch
    } catch (e) {
        logger.error(`Failed to fetch latest epoch number: ${e.message}`)
        throw e
    }
}

/**
 * Get the accounts associated with a given program.
 * @param {string} validatorId - The public key of the validator.
 * @returns {Promise<Object|undefined>} An object containing the accounts associated with the program, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getProgramAccounts = async (validatorId) => {
    try {
        const params = [
            STAKE_PROGRAM_ID,
            {
                commitment: 'confirmed',
                encoding: 'base64',
                dataSize: 200,
                filters: [
                    {
                        memcmp: {
                            offset: 124,
                            bytes: validatorId,
                        },
                    },
                ],
            },
        ]
        const data = await request.POST(
            SOLANA_ENDPOINT,
            populateBody('getProgramAccounts', params)
        )
        return data
    } catch (e) {
        logger.error(`Failed to fetch program accounts: ${e.message}`)
        throw e
    }
}

/**
 * Get information about a specific account.
 * @param {string} pubkey - The public key of the account to query.
 * @returns {Promise<Object|undefined>} An object containing the account information, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getAccountInfo = async (pubkey) => {
    try {
        const data = await request.POST(
            SOLANA_ENDPOINT,
            populateBody('getAccountInfo', [pubkey, { encoding: 'jsonParsed' }])
        )
        return data
    } catch (e) {
        logger.error(`Failed to fetch account info [${pubkey}]: ${e.message}`)
        throw e
    }
}

/**
 * Fetch the Solana price at a specific date.
 * @param {string} timestamp - The timestamp (in ISO 8601 format) for which to fetch the price.
 * @returns {Promise<number|undefined>} The price of Solana in USD at the specified date, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const fetchSolanaPriceAtDate = async (timestamp) => {
    try {
        const d = new Date(timestamp)
        const date = d.getUTCDate()
        const month = d.getUTCMonth() + 1
        const year = d.getUTCFullYear()
        const dateParam = `${date}-${month}-${year}`

        const url = `${EXCHANGE_URL}/coins/solana/history`
        const data = await request.GET(url, {
            params: { localization: false, date: dateParam },
        })
        return data.market_data.current_price.usd
    } catch (e) {
        logger.error(`Failed to fetch Solana price: ${e.message}`)
        throw e
    }
}

/**
 * Fetch the inflation rewards of delegators for a specific epoch.
 * @param {string[]} delegatorPubKeys - An array of public keys of the delegators.
 * @param {number} epoch - The epoch number for which to fetch the rewards.
 * @returns {Promise<Object|undefined>} An object containing the rewards information, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getInflationReward = async (delegatorPubKeys, epoch) => {
    try {
        const body = populateBody('getInflationReward', [
            delegatorPubKeys,
            { epoch },
        ])
        const data = await request.POST(SOLANA_ENDPOINT, body)
        return data
    } catch (e) {
        logger.error(
            `Failed to fetch delegator rewards for epoch: ${epoch} [${delegatorPubKeys.join(
                ', '
            )}]: ${e.message}`
        )
        throw e
    }
}

/**
 * Fetch the block time of a given effective slot.
 * @param {number} effectiveSlot - The effective slot number.
 * @returns {Promise<number|undefined>} The block time in Unix timestamp format, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getBlockTime = async (effectiveSlot) => {
    try {
        const body = populateBody('getBlockTime', [effectiveSlot])
        const { result } = await request.POST(SOLANA_ENDPOINT, body)
        if (!result) logger.error('Block time not found')
        return result
    } catch (e) {
        logger.error(`Failed to fetch block time: ${e.message}`)
        throw e
    }
}

/**
 * Fetch transaction signatures related to an address.
 * @param {string} address - The public key of the account.
 * @returns {Promise<Object|undefined>} An object containing the transaction signatures, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getSignaturesForAddress = async (address) => {
    try {
        const body = populateBody('getSignaturesForAddress', [address])
        const data = await request.POST(SOLANA_ENDPOINT, body)
        return data
    } catch (e) {
        logger.error(
            `Failed to fetch transaction signatures [${address}]: ${e.message}`
        )
        throw e
    }
}

/**
 * Fetch details of a transaction by its signature.
 * @param {string} signature - The transaction signature.
 * @returns {Promise<Object|undefined>} An object containing the transaction details, or undefined if an error occurs.
 * @throws {Error} - If the network request failed.
 */
export const getTransaction = async (signature) => {
    try {
        const body = populateBody('getTransaction', [signature, 'json'])
        const data = await request.POST(SOLANA_ENDPOINT, body)
        return data
    } catch (e) {
        logger.error(
            `Failed to fetch transaction details [${signature}]: ${e.message}`
        )
        throw e
    }
}