From f717173d3dbc15d5ec3c158cbc80eba6117057cf Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Wed, 18 Feb 2026 06:13:31 +0300 Subject: [PATCH] fix: use AH totalIssuance for APY, filter tiny-stake validator outliers --- src/mappings/PoolStakers.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/mappings/PoolStakers.ts b/src/mappings/PoolStakers.ts index 64c0d55..3002de5 100644 --- a/src/mappings/PoolStakers.ts +++ b/src/mappings/PoolStakers.ts @@ -168,16 +168,9 @@ function calculateYearlyInflation(stakedPortion: number): number { } async function computeAndSaveAPY(): Promise { - // Total issuance must come from the relay chain (200M+ HEZ), not AH (7M HEZ). - // AH's balances.totalIssuance only reflects tokens teleported to AH. - // We use staking.erasTotalStake as a proxy: totalStaked is known, and we - // can derive totalIssuance from relay's staking.erasValidatorReward ratio. - // But simplest: read relay's totalIssuance via the staking pallet's - // erasValidatorReward / erasRewardPoints to approximate, OR use a known - // total supply constant. - // - // Pezkuwi total supply ~200M HEZ. For accurate APY we use this. - const TOTAL_SUPPLY = BigInt("200004200000000000000"); // ~200M HEZ in planck + // Use AH's own totalIssuance. AH staking pallet mints inflation from AH supply. + const TOTAL_SUPPLY = ((await api.query.balances.totalIssuance()) as any).toBigInt(); + if (TOTAL_SUPPLY === BigInt(0)) return; const activeEraOpt = (await api.query.staking.activeEra()) as Option; if (activeEraOpt.isNone) return; @@ -214,9 +207,12 @@ async function computeAndSaveAPY(): Promise { const avgRewardPct = yearlyInflation / stakedPortion; const avgStake = totalStaked / BigInt(validators.length); + // Compute per-validator APY, then take the max of validators with + // at least 10% of average stake (filters out tiny-stake outliers) + const minStake = avgStake / BigInt(10); let maxAPY = 0; for (const v of validators) { - if (v.totalStake === BigInt(0)) continue; + if (v.totalStake < minStake) continue; const stakeRatio = Number((avgStake * SCALE) / v.totalStake) / Number(SCALE); const apy = avgRewardPct * stakeRatio * (1 - v.commission); if (apy > maxAPY) maxAPY = apy;