From 64caf5661204cde4f30c3e9149012ebdc908f39c Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Tue, 17 Feb 2026 02:38:46 +0300 Subject: [PATCH] fix: compute staking score from CachedStakingDetails instead of hardcoded 0 --- shared/lib/scores.ts | 87 ++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/shared/lib/scores.ts b/shared/lib/scores.ts index 27b1ec43..a76f0f1f 100644 --- a/shared/lib/scores.ts +++ b/shared/lib/scores.ts @@ -25,7 +25,8 @@ export interface UserScores { export interface StakingScoreStatus { isTracking: boolean; - hasCachedData: boolean; // Whether noter has submitted staking data + hasCachedData: boolean; + score: number; // 0-100 computed from CachedStakingDetails + duration startBlock: number | null; currentBlock: number; durationBlocks: number; @@ -177,58 +178,95 @@ export async function getReferralCount( // ======================================== /** - * Check staking score tracking status - * Storage: stakingScore.stakingStartBlock(address) + * Check staking score tracking status and compute actual staking score. + * Queries CachedStakingDetails from People Chain and calculates score + * using the same formula as pallet_staking_score::get_staking_score(). * - * The stakingScore pallet is on People Chain. It receives staking data - * from Asset Hub via XCM (stored in cachedStakingDetails). + * Score Formula: + * 1. Amount Score (20-50 points based on staked HEZ) + * - 0-100 HEZ: 20, 101-250: 30, 251-750: 40, 751+: 50 + * 2. Duration Multiplier (time since startScoreTracking) + * - <1mo: x1.0, 1-2mo: x1.2, 3-5mo: x1.4, 6-11mo: x1.7, 12+mo: x2.0 + * 3. Final = min(100, floor(amountScore * durationMultiplier)) */ export async function getStakingScoreStatus( peopleApi: ApiPromise, address: string ): Promise { + const empty: StakingScoreStatus = { + isTracking: false, hasCachedData: false, score: 0, + startBlock: null, currentBlock: 0, durationBlocks: 0, + }; + try { if (!peopleApi?.query?.stakingScore?.stakingStartBlock) { - return { isTracking: false, hasCachedData: false, startBlock: null, currentBlock: 0, durationBlocks: 0 }; + return empty; } const startBlockResult = await peopleApi.query.stakingScore.stakingStartBlock(address); const currentBlock = Number((await peopleApi.query.system.number()).toString()); if (startBlockResult.isEmpty || startBlockResult.isNone) { - return { isTracking: false, hasCachedData: false, startBlock: null, currentBlock, durationBlocks: 0 }; + return { ...empty, currentBlock }; } const startBlock = Number(startBlockResult.toString()); const durationBlocks = currentBlock - startBlock; - // Check if noter has submitted cached staking data + // Query CachedStakingDetails for both sources let hasCachedData = false; + let totalStakeWei = BigInt(0); if (peopleApi.query.stakingScore.cachedStakingDetails) { try { const [relayResult, assetHubResult] = await Promise.all([ peopleApi.query.stakingScore.cachedStakingDetails(address, 'RelayChain') - .catch(() => ({ isSome: false, isEmpty: true })), + .catch(() => null), peopleApi.query.stakingScore.cachedStakingDetails(address, 'AssetHub') - .catch(() => ({ isSome: false, isEmpty: true })), + .catch(() => null), ]); - hasCachedData = (relayResult.isSome || !relayResult.isEmpty) || - (assetHubResult.isSome || !assetHubResult.isEmpty); + if (relayResult && !relayResult.isEmpty && relayResult.isSome) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = (relayResult.unwrap() as any).toJSON(); + totalStakeWei += BigInt(json.stakedAmount ?? json.staked_amount ?? '0'); + hasCachedData = true; + } + if (assetHubResult && !assetHubResult.isEmpty && assetHubResult.isSome) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = (assetHubResult.unwrap() as any).toJSON(); + totalStakeWei += BigInt(json.stakedAmount ?? json.staked_amount ?? '0'); + hasCachedData = true; + } } catch { - hasCachedData = false; + // keep defaults } } - return { - isTracking: true, - hasCachedData, - startBlock, - currentBlock, - durationBlocks - }; + // Calculate staking score from cached data + let score = 0; + if (hasCachedData && totalStakeWei > BigInt(0)) { + const stakedHEZ = Number(totalStakeWei / BigInt(10 ** 12)); + + // Amount tier + let amountScore = 20; + if (stakedHEZ > 750) amountScore = 50; + else if (stakedHEZ > 250) amountScore = 40; + else if (stakedHEZ > 100) amountScore = 30; + + // Duration multiplier + const MONTH = 432000; // ~30 days in blocks (6s per block) + let mult = 1.0; + if (durationBlocks >= 12 * MONTH) mult = 2.0; + else if (durationBlocks >= 6 * MONTH) mult = 1.7; + else if (durationBlocks >= 3 * MONTH) mult = 1.4; + else if (durationBlocks >= MONTH) mult = 1.2; + + score = Math.min(100, Math.floor(amountScore * mult)); + } + + return { isTracking: true, hasCachedData, score, startBlock, currentBlock, durationBlocks }; } catch (error) { console.error('Error fetching staking score status:', error); - return { isTracking: false, hasCachedData: false, startBlock: null, currentBlock: 0, durationBlocks: 0 }; + return empty; } } @@ -312,18 +350,19 @@ export async function getAllScores( } try { - const [trustScore, referralScore, tikiScore] = await Promise.all([ + const [trustScore, referralScore, tikiScore, stakingStatus] = await Promise.all([ getTrustScore(peopleApi, address), getReferralScore(peopleApi, address), getTikiScore(peopleApi, address), + getStakingScoreStatus(peopleApi, address), ]); return { trustScore, referralScore, - stakingScore: 0, // Trust pallet already includes staking + stakingScore: stakingStatus.score, tikiScore, - totalScore: trustScore, // Trust score = composite score (on-chain calculated) + totalScore: trustScore, }; } catch (error) { console.error('Error fetching scores:', error);