fix: compute staking score from CachedStakingDetails instead of hardcoded 0

This commit is contained in:
2026-02-17 02:38:46 +03:00
parent b7d7d008dc
commit 64caf56612
+63 -24
View File
@@ -25,7 +25,8 @@ export interface UserScores {
export interface StakingScoreStatus { export interface StakingScoreStatus {
isTracking: boolean; isTracking: boolean;
hasCachedData: boolean; // Whether noter has submitted staking data hasCachedData: boolean;
score: number; // 0-100 computed from CachedStakingDetails + duration
startBlock: number | null; startBlock: number | null;
currentBlock: number; currentBlock: number;
durationBlocks: number; durationBlocks: number;
@@ -177,58 +178,95 @@ export async function getReferralCount(
// ======================================== // ========================================
/** /**
* Check staking score tracking status * Check staking score tracking status and compute actual staking score.
* Storage: stakingScore.stakingStartBlock(address) * 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 * Score Formula:
* from Asset Hub via XCM (stored in cachedStakingDetails). * 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( export async function getStakingScoreStatus(
peopleApi: ApiPromise, peopleApi: ApiPromise,
address: string address: string
): Promise<StakingScoreStatus> { ): Promise<StakingScoreStatus> {
const empty: StakingScoreStatus = {
isTracking: false, hasCachedData: false, score: 0,
startBlock: null, currentBlock: 0, durationBlocks: 0,
};
try { try {
if (!peopleApi?.query?.stakingScore?.stakingStartBlock) { 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 startBlockResult = await peopleApi.query.stakingScore.stakingStartBlock(address);
const currentBlock = Number((await peopleApi.query.system.number()).toString()); const currentBlock = Number((await peopleApi.query.system.number()).toString());
if (startBlockResult.isEmpty || startBlockResult.isNone) { if (startBlockResult.isEmpty || startBlockResult.isNone) {
return { isTracking: false, hasCachedData: false, startBlock: null, currentBlock, durationBlocks: 0 }; return { ...empty, currentBlock };
} }
const startBlock = Number(startBlockResult.toString()); const startBlock = Number(startBlockResult.toString());
const durationBlocks = currentBlock - startBlock; const durationBlocks = currentBlock - startBlock;
// Check if noter has submitted cached staking data // Query CachedStakingDetails for both sources
let hasCachedData = false; let hasCachedData = false;
let totalStakeWei = BigInt(0);
if (peopleApi.query.stakingScore.cachedStakingDetails) { if (peopleApi.query.stakingScore.cachedStakingDetails) {
try { try {
const [relayResult, assetHubResult] = await Promise.all([ const [relayResult, assetHubResult] = await Promise.all([
peopleApi.query.stakingScore.cachedStakingDetails(address, 'RelayChain') peopleApi.query.stakingScore.cachedStakingDetails(address, 'RelayChain')
.catch(() => ({ isSome: false, isEmpty: true })), .catch(() => null),
peopleApi.query.stakingScore.cachedStakingDetails(address, 'AssetHub') peopleApi.query.stakingScore.cachedStakingDetails(address, 'AssetHub')
.catch(() => ({ isSome: false, isEmpty: true })), .catch(() => null),
]); ]);
hasCachedData = (relayResult.isSome || !relayResult.isEmpty) || if (relayResult && !relayResult.isEmpty && relayResult.isSome) {
(assetHubResult.isSome || !assetHubResult.isEmpty); // 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 { } catch {
hasCachedData = false; // keep defaults
} }
} }
return { // Calculate staking score from cached data
isTracking: true, let score = 0;
hasCachedData, if (hasCachedData && totalStakeWei > BigInt(0)) {
startBlock, const stakedHEZ = Number(totalStakeWei / BigInt(10 ** 12));
currentBlock,
durationBlocks // 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) { } catch (error) {
console.error('Error fetching staking score status:', 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 { try {
const [trustScore, referralScore, tikiScore] = await Promise.all([ const [trustScore, referralScore, tikiScore, stakingStatus] = await Promise.all([
getTrustScore(peopleApi, address), getTrustScore(peopleApi, address),
getReferralScore(peopleApi, address), getReferralScore(peopleApi, address),
getTikiScore(peopleApi, address), getTikiScore(peopleApi, address),
getStakingScoreStatus(peopleApi, address),
]); ]);
return { return {
trustScore, trustScore,
referralScore, referralScore,
stakingScore: 0, // Trust pallet already includes staking stakingScore: stakingStatus.score,
tikiScore, tikiScore,
totalScore: trustScore, // Trust score = composite score (on-chain calculated) totalScore: trustScore,
}; };
} catch (error) { } catch (error) {
console.error('Error fetching scores:', error); console.error('Error fetching scores:', error);