ICP/SNS staking rewards calculation?

Anybody have the calculation scripts for ICP and SNS projects?

1 Like

I think you are looking for fn distribute_rewards.

I’m looking for a way to calculate the estimated APY when creating a neuron, specifically the Voting rewards calculation on the dashboard:

Hello @dostro
For the ICP Dashboard here is how we fetch the data for the Estimate Rewards

  const fetchData =  async () => {
      try {
        // https://ic-api.internetcomputer.org/api/v3/governance-metrics
        const response = await queryApi('v3/governance-metrics', ApiType.IC);

        const lastRewardRoundTotalAvailableE8s: any = response.data.metrics.find((element: any) => {
          return element.name === 'governance_latest_reward_round_total_available_e8s'
        });

        const lastRewardsRoundTotalAvailableIcp: number =
          Number(lastRewardRoundTotalAvailableE8s.subsets[0].value[1]) / 100000000;
        setLastRewardsRoundTotalAvailableIcp(lastRewardsRoundTotalAvailableIcp);

        const totalVotingPowerE8s: any = response.data.metrics.find((element: any) => {
          return element.name === 'governance_voting_power_total'
        });
        const totalVotingPower: number =
          Number(totalVotingPowerE8s.subsets[0].value[1]) / 100000000;
        setTotalVotingPower(totalVotingPower);
      } catch {
        setLastRewardsRoundTotalAvailableIcp(undefined);
        setTotalVotingPower(undefined);
      }
    };

And here is how we do the data calculations

    if (lastRewardsRoundTotalAvailableIcp !== undefined
      && totalVotingPower !== undefined
      && totalVotingPower > 0) {

      let estimatedRewardsPercentage: number | undefined;
      let estimatedNeuronDailyRewards: number | undefined;
      if (sliderState.dissolveDelayYears >= 0.5) {
        const dissolveDelayBonusSlider: number = 1 + sliderState.dissolveDelayYears / 8;
        const dailyRewardsIcpPerVotingPowerUnit: number =
          lastRewardsRoundTotalAvailableIcp / totalVotingPower;

        let ageBonus: number;
        if (neuronInfo !== undefined) {
          ageBonus =
            1 +
            Math.min(neuronInfo.ageSeconds, Constants.governanceMaxNeuronAgeForAgeBonusSeconds) /
            Constants.governanceMaxNeuronAgeForAgeBonusSeconds / 4;

          const dissolveDelayBonusActual: number =
            1 + (NeuronInfo.getDissolveDelayBonusPercentage(neuronInfo) ?? 0) / 100;
          estimatedNeuronDailyRewards =
            neuronInfo.votingPowerE8s / 100000000 *
            dailyRewardsIcpPerVotingPowerUnit *
            dissolveDelayBonusSlider / dissolveDelayBonusActual;

        } else
          ageBonus = 1;

        const estimatedRewardsIcpPerVotingPowerUnit: number =
          dailyRewardsIcpPerVotingPowerUnit * dissolveDelayBonusSlider * ageBonus;
        estimatedRewardsPercentage = estimatedRewardsIcpPerVotingPowerUnit * 365.25 * 100;

      } else {
        estimatedRewardsPercentage = 0;
        estimatedNeuronDailyRewards = neuronInfo !== undefined ? 0 : undefined;
      }
      setEstimatedRewardsPercentage(estimatedRewardsPercentage);
      setEstimatedNeuronDailyRewards(estimatedNeuronDailyRewards);
    } 
  }
4 Likes

Thank you! Will take a look in the coming weeks and let you know how it goes.

2 Likes

hi @ngurian,
could you please take a look?

I have a private method that returns dailyRewards (lastRewardsRoundTotalAvailableIcp / totalVotingPower)

private async getIcpDashboardData(): Promise<
    | {
        dailyRewardsPerVotingPowerUnit: number
      }
    | undefined
  > {
    if (this.icpDashboardData) return this.icpDashboardData

    try {
      const response = await fetch(
        "https://ic-api.internetcomputer.org/api/v3/governance-metrics",
      )

      const data = await response.json()

      const latestRewards =
        Number(
          data.metrics.find(
            (element: any) =>
              element.name ===
              "governance_latest_reward_round_total_available_e8s",
          ).subsets[0].value[1],
        ) /
        10 ** this.token.getTokenDecimals()

      const votingPower: any =
        Number(
          data.metrics.find(
            (element: any) => element.name === "governance_voting_power_total",
          ).subsets[0].value[1],
        ) /
        10 ** this.token.getTokenDecimals()

      this.icpDashboardData = {
        dailyRewardsPerVotingPowerUnit: latestRewards / votingPower,
      }

      return this.icpDashboardData
    } catch (e) {
      console.error("ICP Dahsboard Error: ", e)
    }
  }

Also I have two separate methods for calculating apr and rewards:

  1. APR:
    Here I can’t understand what is 4 (ageBonus last devide action)? Is it a constant value or where it can be taken?
async calculateEstAPR(
    lockValueInMonths: number,
    lockValue: number,
  ): Promise<string | undefined> {
    const data = await this.getIcpDashboardData()
    if (data === undefined) return

    const dissolveDelayBonusSlider =
      1 + lockValueInMonths / (this.getMaximumLockTimeInMonths() / 12)

    const ageBonus =
      1 +
      Math.min(lockValue, Number(this.params.max_neuron_age_for_age_bonus[0])) /
        Number(this.params.max_neuron_age_for_age_bonus[0]) /
        4

    const estimatedRewardsIcpPerVotingPowerUnit: number =
    data.dailyRewardsPerVotingPowerUnit * dissolveDelayBonusSlider * ageBonus

    return `${(estimatedRewardsIcpPerVotingPowerUnit * 365.25 * 100).toFixed(
      2,
    )}%`
  }
  1. Projected Rewards:
    I am confused where should I take the votingPowerE8s variable??
async calculateProjectRewards(
    amount: string,
    lockValueInMonths: number,
  ): Promise<TokenValue | undefined> {
    const data = await this.getIcpDashboardData()
    if (data === undefined) return
    const { dailyRewardsPerVotingPowerUnit } = data

    const dissolveDelayBonusSlider =
      1 + lockValueInMonths / 12 / this.getMaximumLockTimeInMonths() / 12

    const dissolveDelayBonusActual =
      1 +
      (Number(this.params.max_dissolve_delay_bonus_percentage[0]) ?? 0) / 100

    const votingPowerE8s = 1 // ???

    const estimatedNeuronDailyRewards =
      ((votingPowerE8s / 10 ** this.token.getTokenDecimals()) *
        dailyRewardsPerVotingPowerUnit *
        dissolveDelayBonusSlider) /
      dissolveDelayBonusActual

    const value = (estimatedNeuronDailyRewards * lockValueInMonths * 30)
      .toFixed(this.token.getTokenDecimals())
      .replace(TRIM_ZEROS, "")

    return {
      getTokenValue: () => `${value} ${this.token.getTokenSymbol()}`,
      getUSDValue: () =>
        this.token.getTokenRateFormatted(value) || "Not listed",
    }
  }
1 Like

@ngurian could you please help Vitalii with his questions? we’re still blocked here

Dividing by 4 scales the age bonus linearly from 0% to 25%, based on the neuron’s age.

The code is currently doing this:

ageBonus = 1 + (ageRatio / 4);

The code below does the same thing, and using 0.25 in the calculation like this probably would be a bit more clear than using 4:

ageBonus = 1 + (ageRatio * 0.25);

An SNS sets its own voting rewards parameters, so if writing code for an SNS neuron you would use those parameters rather than scaling from 0% to 25%.

For an NNS neuron, you can call get_neuron_info on the NNS Governance canister.

For SNS neurons, the SNS Governance canister unfortunately does not return the voting power, so you need to calculate it yourself. The source of truth for how to do this is the SNS Governance code.