Cover photo

Block Reward Distribution Rules

Overview

Winners receive block rewards based on the impressions of the posts they submitted.

Specifically, points are calculated using the following formula, and block rewards are distributed proportionally based on these points:

Point={0ifimp<50, impif50imp<1,000,000 1000ifimp1,000,000  imp:Impressions of the submitted post\begin{gather*} Point = \begin{cases} 0 & \text{if} \quad imp < 50, \ \sqrt{imp} & \text{if} \quad 50 \leq imp < 1,000,000 \ 1000 & \text{if} \quad imp \geq 1,000,000 \end{cases}\ \quad\ \quad\quad\quad{\small imp : \text{Impressions of the submitted post}} \end{gather*}

First, to prevent mining by a large number of bot accounts, impressions below 50 are assigned a point value of 0.

Next, to prevent excessive concentration of rewards on influencers and promote decentralization, points are determined as the square root of impressions. This means that even if impressions increase by a factor of 100, the reward only increases by a factor of 10.

post image

Furthermore, if impressions exceed 1,000,000, the point value is capped at 1000. For example, if a typical post receives 100 impressions, its point value would be 10, meaning that even in the maximum case, a single viral post would receive no more than approximately 100 times the reward of a typical post. This mechanism prevents a single viral post from monopolizing most of the block rewards.

post image

Additional Notes

  • There is no difference in point calculation between verified and unverified winners.

  • In proportional distribution, amounts below 1 satoshi are truncated. The resulting remainder is allocated to the winner with the highest impressions. (If multiple winners have the same number of impressions, priority is given to the earliest verified user to apply.)

Program Code

/**
 * Function to calculate points from impressions
 * @param {number} impression - Number of impressions
 * @return {number} - Calculated points
 */
function calculatePoints(impression) {
  // If 50 or below, points are 0
  if (impression <= 50) {
    return 0;
  }
  
  // If 1,000,000 or more, calculate as 1,000,000
  const cappedImpression = Math.min(impression, 1000000);
  
  // Points are determined as the square root of impressions
  return Math.sqrt(cappedImpression);
}

/**
 * Function to add points to the list of winners
 * @param {Array<Object>} winnersList - List of winners
 * @return {Array<Object>} - List of winners with added points
 */
function addPointsToWinners(winnersList) {
  return winnersList.map(winner => {
    // Create a new object without modifying the existing one
    return {
      ...winner,
      point: calculatePoints(winner.impression)
    };
  });
}

// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    calculatePoints,
    addPointsToWinners
  };
}

/**
 * Function to distribute Bitcoin rewards to winners in proportion to their points
 * @param {Array<Object>} winnersWithPoints - List of winners with added points
 * @param {number} blockReward - Total reward to be distributed (in satoshis)
 * @return {Array<Object>} - List of winners with added rewards
 */
function distributeReward(winnersWithPoints, blockReward) {
  // Calculate total points
  const totalPoints = winnersWithPoints.reduce((sum, winner) => sum + winner.point, 0);
  
  // If total points are zero, do not distribute rewards
  if (totalPoints === 0) {
    return winnersWithPoints.map(winner => ({
      ...winner,
      reward: 0
    }));
  }
  
  // Calculate initial distribution (truncated)
  const winnersWithReward = winnersWithPoints.map(winner => {
    const rewardAmount = Math.floor((winner.point / totalPoints) * blockReward);
    return {
      ...winner,
      reward: rewardAmount
    };
  });
  
  // Calculate the total amount of the initial distribution
  const distributedReward = winnersWithReward.reduce((sum, winner) => sum + winner.reward, 0);
  
  // Remaining reward (unallocated fraction)
  const remainingReward = blockReward - distributedReward;
  
  // If there is remaining reward, allocate it to the winner with the highest impressions
  if (remainingReward > 0) {
    // Find the index of the winner with the highest impressions
    let maxImpressionIndex = 0;
    let maxImpression = winnersWithReward[0].impression;
    
    for (let i = 1; i < winnersWithReward.length; i++) {
      if (winnersWithReward[i].impression > maxImpression) {
        maxImpression = winnersWithReward[i].impression;
        maxImpressionIndex = i;
      }
    }
    
    // Allocate all remaining rewards to the winner with the highest impressions
    winnersWithReward[maxImpressionIndex].reward += remainingReward;
  }
  
  return winnersWithReward;
}

/**
 * Function to merge the lists of verified and unverified winners and distribute rewards
 * @param {Array<Object>} verifiedWinners - List of verified winners
 * @param {Array<Object>} unverifiedWinners - List of unverified winners
 * @param {number} blockReward - Total reward to be distributed (in satoshis)
 * @return {Array<Object>} - List of winners with distributed rewards
 */
function mergeAndDistributeReward(verifiedWinners, unverifiedWinners, blockReward) {
  // Merge both lists
  const allWinners = [...verifiedWinners, ...unverifiedWinners];
  
  // Calculate points
  const winnersWithPoints = addPointsToWinners(allWinners);
  
  // Distribute rewards
  const winnersWithReward = distributeReward(winnersWithPoints, blockReward);
  
  return winnersWithReward;
}

// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    calculatePoints,
    addPointsToWinners,
    distributeReward,
    mergeAndDistributeReward
  };
}

// Export to global scope for use in Cloudflare Workers
if (typeof self !== 'undefined') {
  self.calculatePoints = calculatePoints;
  self.addPointsToWinners = addPointsToWinners;
  self.distributeReward = distributeReward;
  self.mergeAndDistributeReward = mergeAndDistributeReward;
}