cyb/src/pages/Mining/utils/simulate.ts

/**
 * Simulation engine for Lithium mining โ€” adaptive hybrid economics.
 * Pure function: SimParams โ†’ SimResult.
 *
 * In the new model:
 * - No epochs โ€” continuous sliding window
 * - Client-chosen difficulty d โ†’ reward = base_rate * d
 * - base_rate = (G * R_pow_share) / D_rate
 * - G = emission_rate + fee_rate * (1 - beta)
 * - R_pow_share = 1 - S^alpha
 */

import {
  totalRateAtomicPerSecond,
  totalEmittedAtomic,
  componentRates,
  atomicToHuman,
  LI_TOTAL_SUPPLY_ATOMIC,
  SECONDS_PER_DAY,
  type ComponentRate,
} from './emission';

export type SimParams = {
  elapsedDays: number; // 0 โ†’ 7300 (20 years)
  networkHashrate: number; // H/s
  yourHashrate: number; // H/s (personal)
  stakedPercent: number; // 0-100
  dailyTransfers: number; // for fee projection
  difficultyBits: number; // client-chosen difficulty
  liPrice: number; // USD per LI
  alpha: number; // PID alpha [0.3, 0.7]
  beta: number; // PID beta [0.0, 0.9]
  gasCostUboot: number; // gas cost per proof in uboot
};

export type SimResult = {
  components: ComponentRate[];
  emissionPerHour: number;
  emissionPerSecond: number;
  miningPercent: number;
  stakingPercent: number;
  referralPercent: number;
  alpha: number;
  difficultyBits: number;
  baseRate: number;
  rewardPerProof: number;
  totalMinted: number;
  mintedPercent: number;
  dailyFeeBurn: number;
  netDailyInflation: number;
  gasCostPerProofLi: number;
  netRewardPerProof: number;
  // Per-split emission (human LI per hour)
  miningEmissionPerHour: number;
  stakingEmissionPerHour: number;
  referralEmissionPerHour: number;
  // Personal stats
  yourProofsPerDay: number;
  yourLiPerDay: number;
  yourLiPerMonth: number;
  timePerProofSeconds: number;
  yourNetworkSharePercent: number;
  yourDailyUsd: number;
  yourMonthlyUsd: number;
};

const REFERRAL_SHARE = 0.1; // 10%
const FEE_PER_TRANSFER = 0.01; // 1% fee per transfer
const AVERAGE_TRANSFER_AMOUNT = 50; // average LI per transfer (assumption)

function computeSplit(stakedFraction: number, alpha: number) {
  // Spec ยง5: 10% referral from gross, 90% split between stakers/miners
  const sAlpha = stakedFraction > 0 && alpha > 0 ? Math.pow(stakedFraction, alpha) : 0;
  const postReferral = 1 - REFERRAL_SHARE; // 90% of gross

  // Percentages of gross emission going to each recipient
  const referralPercent = REFERRAL_SHARE * 100; // 10%
  const miningPercent = postReferral * (1 - sAlpha) * 100;
  const stakingPercent = postReferral * sAlpha * 100;

  // powShare = miner's fraction of gross (for base_rate calibration)
  const powShare = postReferral * (1 - sAlpha);
  const posShare = postReferral * sAlpha;
  return {
    miningPercent,
    stakingPercent,
    referralPercent,
    powShare,
    posShare,
  };
}

export function simulate(params: SimParams): SimResult {
  const elapsedSeconds = params.elapsedDays * SECONDS_PER_DAY;

  const components = componentRates(elapsedSeconds);
  const totalRateAtomic = totalRateAtomicPerSecond(elapsedSeconds);
  const totalMintedAtomic = totalEmittedAtomic(elapsedSeconds);

  const emissionPerSecond = atomicToHuman(totalRateAtomic);

  const stakedFraction = Math.max(0, Math.min(1, params.stakedPercent / 100));
  const alpha = params.alpha;
  const beta = params.beta;

  const { miningPercent, stakingPercent, referralPercent, powShare } =
    computeSplit(stakedFraction, alpha);

  // Fee estimation
  const dailyFeeVolume = params.dailyTransfers * AVERAGE_TRANSFER_AMOUNT * FEE_PER_TRANSFER;
  const feeRatePerSecond = dailyFeeVolume / SECONDS_PER_DAY;

  // Gross rate G = emission + fees * (1 - beta)
  const grossRatePerSecond = emissionPerSecond + feeRatePerSecond * (1 - beta);

  // D_rate estimation: all network hashrate is computing at the same difficulty
  // D_rate = proofRate * avgDifficulty
  // proofRate = networkHashrate / 2^difficulty
  const hashesPerProof = params.difficultyBits > 0 ? 2 ** params.difficultyBits : 1;
  const networkProofRate = params.networkHashrate / hashesPerProof; // proofs/sec
  const dRate = networkProofRate * params.difficultyBits; // difficulty bits/sec

  // base_rate = (G * R_pow_share) / D_rate
  const baseRate = dRate > 0
    ? (grossRatePerSecond * powShare) / dRate
    : 0;

  // reward per proof = base_rate * d
  // powShare already includes the post-referral factor (0.9), so no extra cut needed
  const rewardPerProof = baseRate * params.difficultyBits;

  // Per-split emission (per hour for readability)
  const emissionPerHour = emissionPerSecond * 3600;
  const miningEmissionPerHour = emissionPerHour * (miningPercent / 100);
  const stakingEmissionPerHour = emissionPerHour * (stakingPercent / 100);
  const referralEmissionPerHour = emissionPerHour * (referralPercent / 100);

  const totalMinted = atomicToHuman(totalMintedAtomic);
  const mintedPercent = (totalMintedAtomic / LI_TOTAL_SUPPLY_ATOMIC) * 100;

  const dailyFeeBurn = dailyFeeVolume * beta; // beta portion is burned
  const dailyEmission = emissionPerSecond * SECONDS_PER_DAY;
  const netDailyInflation = dailyEmission - dailyFeeBurn;

  // Gas cost per proof (uboot โ†’ LI conversion: approximate, price-dependent)
  // For display we show the raw uboot cost; if liPrice > 0 we can show USD equiv
  const gasCostPerProofLi = params.gasCostUboot / 1_000_000; // uboot to BOOT (6 decimals)
  const netRewardPerProof = Math.max(0, rewardPerProof - gasCostPerProofLi * params.liPrice);

  // Personal stats
  const yourProofsPerSecond =
    hashesPerProof > 0 ? params.yourHashrate / hashesPerProof : 0;
  const yourProofsPerDay = yourProofsPerSecond * SECONDS_PER_DAY;
  const yourLiPerDay = yourProofsPerDay * rewardPerProof;
  const yourLiPerMonth = yourLiPerDay * 30;
  const timePerProofSeconds =
    yourProofsPerSecond > 0 ? 1 / yourProofsPerSecond : Infinity;
  const yourNetworkSharePercent =
    params.networkHashrate > 0
      ? (params.yourHashrate / params.networkHashrate) * 100
      : 0;
  const yourDailyUsd = yourLiPerDay * params.liPrice;
  const yourMonthlyUsd = yourLiPerMonth * params.liPrice;

  return {
    components,
    emissionPerHour,
    emissionPerSecond,
    miningPercent,
    stakingPercent,
    referralPercent,
    alpha,
    difficultyBits: params.difficultyBits,
    baseRate,
    rewardPerProof,
    totalMinted,
    mintedPercent,
    dailyFeeBurn,
    netDailyInflation,
    gasCostPerProofLi,
    netRewardPerProof,
    miningEmissionPerHour,
    stakingEmissionPerHour,
    referralEmissionPerHour,
    yourProofsPerDay,
    yourLiPerDay,
    yourLiPerMonth,
    timePerProofSeconds,
    yourNetworkSharePercent,
    yourDailyUsd,
    yourMonthlyUsd,
  };
}

Neighbours