mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-13 11:11:01 +00:00
feat: add frontend fallback for staking and trust scores
Until runtime upgrade is deployed, calculate scores on frontend: shared/lib/scores.ts: - getFrontendStakingScore: Read stake from Relay Chain, track in localStorage - getFrontendTrustScore: Calculate using pallet formula - getAllScoresWithFallback: Combined score fetching with fallback Formula (matching pezpallet-trust): - weighted_sum = staking*100 + referral*300 + perwerde*300 + tiki*300 - trust_score = (staking * weighted_sum) / 100 Components updated: - AccountBalance.tsx: Use getAllScoresWithFallback - HeroSection.tsx: Use getTrustScoreWithFallback
This commit is contained in:
+519
-8
@@ -287,10 +287,11 @@ export async function getTikiScore(
|
|||||||
/**
|
/**
|
||||||
* Fetch all scores for a user in one call
|
* Fetch all scores for a user in one call
|
||||||
* All scores come from People Chain except staking amount (Relay Chain)
|
* All scores come from People Chain except staking amount (Relay Chain)
|
||||||
|
* Staking score uses frontend fallback if on-chain pallet is not available
|
||||||
*
|
*
|
||||||
* @param peopleApi - People Chain API (trust, referral, stakingScore, tiki pallets)
|
* @param peopleApi - People Chain API (trust, referral, stakingScore, tiki pallets)
|
||||||
* @param address - User's blockchain address
|
* @param address - User's blockchain address
|
||||||
* @param relayApi - Optional Relay Chain API (for staking.ledger amount)
|
* @param relayApi - Optional Relay Chain API (for staking.ledger amount and frontend fallback)
|
||||||
*/
|
*/
|
||||||
export async function getAllScores(
|
export async function getAllScores(
|
||||||
peopleApi: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
@@ -298,7 +299,7 @@ export async function getAllScores(
|
|||||||
relayApi?: ApiPromise
|
relayApi?: ApiPromise
|
||||||
): Promise<UserScores> {
|
): Promise<UserScores> {
|
||||||
try {
|
try {
|
||||||
if (!peopleApi || !address) {
|
if (!address) {
|
||||||
return {
|
return {
|
||||||
trustScore: 0,
|
trustScore: 0,
|
||||||
referralScore: 0,
|
referralScore: 0,
|
||||||
@@ -308,12 +309,28 @@ export async function getAllScores(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all scores in parallel from People Chain
|
// Fetch all scores in parallel
|
||||||
const [trustScore, referralScore, stakingScore, tikiScore] = await Promise.all([
|
const scorePromises: Promise<number>[] = [];
|
||||||
getTrustScore(peopleApi, address),
|
|
||||||
getReferralScore(peopleApi, address),
|
// Trust and referral scores from People Chain
|
||||||
getStakingScore(peopleApi, address, relayApi),
|
if (peopleApi) {
|
||||||
getTikiScore(peopleApi, address)
|
scorePromises.push(getTrustScore(peopleApi, address));
|
||||||
|
scorePromises.push(getReferralScore(peopleApi, address));
|
||||||
|
scorePromises.push(getTikiScore(peopleApi, address));
|
||||||
|
} else {
|
||||||
|
scorePromises.push(Promise.resolve(0));
|
||||||
|
scorePromises.push(Promise.resolve(0));
|
||||||
|
scorePromises.push(Promise.resolve(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Staking score with frontend fallback
|
||||||
|
const stakingScorePromise = relayApi
|
||||||
|
? getStakingScoreWithFallback(peopleApi, relayApi, address).then(r => r.score)
|
||||||
|
: Promise.resolve(0);
|
||||||
|
|
||||||
|
const [trustScore, referralScore, tikiScore, stakingScore] = await Promise.all([
|
||||||
|
...scorePromises,
|
||||||
|
stakingScorePromise
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const totalScore = trustScore + referralScore + stakingScore + tikiScore;
|
const totalScore = trustScore + referralScore + stakingScore + tikiScore;
|
||||||
@@ -378,3 +395,497 @@ export function formatDuration(blocks: number): string {
|
|||||||
if (hours >= 1) return `${Math.floor(hours)} hour${Math.floor(hours) > 1 ? 's' : ''}`;
|
if (hours >= 1) return `${Math.floor(hours)} hour${Math.floor(hours) > 1 ? 's' : ''}`;
|
||||||
return `${Math.floor(minutes)} minute${Math.floor(minutes) > 1 ? 's' : ''}`;
|
return `${Math.floor(minutes)} minute${Math.floor(minutes) > 1 ? 's' : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// FRONTEND STAKING SCORE (Fallback)
|
||||||
|
// ========================================
|
||||||
|
// Until runtime upgrade deploys, calculate staking score
|
||||||
|
// directly from Relay Chain data without People Chain pallet
|
||||||
|
|
||||||
|
const STAKING_SCORE_STORAGE_KEY = 'pez_staking_score_tracking';
|
||||||
|
const UNITS = 1_000_000_000_000; // 10^12
|
||||||
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||||
|
const MONTH_IN_MS = 30 * DAY_IN_MS;
|
||||||
|
|
||||||
|
interface StakingTrackingData {
|
||||||
|
[address: string]: {
|
||||||
|
startTime: number; // Unix timestamp in ms
|
||||||
|
lastChecked: number;
|
||||||
|
lastStakeAmount: string; // Store as string to preserve precision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tracking data from localStorage
|
||||||
|
*/
|
||||||
|
function getStakingTrackingData(): StakingTrackingData {
|
||||||
|
if (typeof window === 'undefined') return {};
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STAKING_SCORE_STORAGE_KEY);
|
||||||
|
return stored ? JSON.parse(stored) : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save tracking data to localStorage
|
||||||
|
*/
|
||||||
|
function saveStakingTrackingData(data: StakingTrackingData): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STAKING_SCORE_STORAGE_KEY, JSON.stringify(data));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to save staking tracking data:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch staking details directly from Relay Chain
|
||||||
|
*/
|
||||||
|
export async function fetchRelayStakingDetails(
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<{ stakedAmount: bigint; nominationsCount: number } | null> {
|
||||||
|
try {
|
||||||
|
if (!relayApi?.query?.staking) return null;
|
||||||
|
|
||||||
|
// Try to get ledger directly (if address is controller)
|
||||||
|
let ledger = await relayApi.query.staking.ledger?.(address);
|
||||||
|
let stashAddress = address;
|
||||||
|
|
||||||
|
// If no ledger, check if this is a stash account
|
||||||
|
if (!ledger || ledger.isEmpty || ledger.isNone) {
|
||||||
|
const bonded = await relayApi.query.staking.bonded?.(address);
|
||||||
|
if (bonded && !bonded.isEmpty && !bonded.isNone) {
|
||||||
|
// This is a stash, get controller's ledger
|
||||||
|
const controller = bonded.toString();
|
||||||
|
ledger = await relayApi.query.staking.ledger?.(controller);
|
||||||
|
stashAddress = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ledger || ledger.isEmpty || ledger.isNone) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse ledger data
|
||||||
|
const ledgerJson = ledger.toJSON() as { active?: string | number };
|
||||||
|
const active = BigInt(ledgerJson?.active || 0);
|
||||||
|
|
||||||
|
// Get nominations
|
||||||
|
const nominations = await relayApi.query.staking.nominators?.(stashAddress);
|
||||||
|
const nominationsJson = nominations?.toJSON() as { targets?: unknown[] } | null;
|
||||||
|
const nominationsCount = nominationsJson?.targets?.length || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
stakedAmount: active,
|
||||||
|
nominationsCount
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch relay staking details:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate base score from staked HEZ amount (matching pallet algorithm)
|
||||||
|
*/
|
||||||
|
function calculateBaseStakingScore(stakedHez: number): number {
|
||||||
|
if (stakedHez <= 0) return 0;
|
||||||
|
if (stakedHez <= 100) return 20;
|
||||||
|
if (stakedHez <= 250) return 30;
|
||||||
|
if (stakedHez <= 750) return 40;
|
||||||
|
return 50; // 751+ HEZ
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get time multiplier based on months staked (matching pallet algorithm)
|
||||||
|
*/
|
||||||
|
function getStakingTimeMultiplier(monthsStaked: number): number {
|
||||||
|
if (monthsStaked >= 12) return 2.0;
|
||||||
|
if (monthsStaked >= 6) return 1.7;
|
||||||
|
if (monthsStaked >= 3) return 1.4;
|
||||||
|
if (monthsStaked >= 1) return 1.2;
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontendStakingScoreResult {
|
||||||
|
score: number;
|
||||||
|
stakedAmount: bigint;
|
||||||
|
stakedHez: number;
|
||||||
|
trackingStarted: Date | null;
|
||||||
|
monthsStaked: number;
|
||||||
|
timeMultiplier: number;
|
||||||
|
nominationsCount: number;
|
||||||
|
isFromFrontend: boolean; // true = frontend fallback, false = on-chain
|
||||||
|
needsRefresh: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get staking score using frontend fallback
|
||||||
|
* This bypasses the broken People Chain pallet and reads directly from Relay Chain
|
||||||
|
*
|
||||||
|
* @param relayApi - Relay Chain API (staking pallet)
|
||||||
|
* @param address - User's blockchain address
|
||||||
|
*/
|
||||||
|
export async function getFrontendStakingScore(
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<FrontendStakingScoreResult> {
|
||||||
|
const emptyResult: FrontendStakingScoreResult = {
|
||||||
|
score: 0,
|
||||||
|
stakedAmount: 0n,
|
||||||
|
stakedHez: 0,
|
||||||
|
trackingStarted: null,
|
||||||
|
monthsStaked: 0,
|
||||||
|
timeMultiplier: 1.0,
|
||||||
|
nominationsCount: 0,
|
||||||
|
isFromFrontend: true,
|
||||||
|
needsRefresh: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!relayApi || !address) return emptyResult;
|
||||||
|
|
||||||
|
// Fetch staking details from Relay Chain
|
||||||
|
const details = await fetchRelayStakingDetails(relayApi, address);
|
||||||
|
|
||||||
|
if (!details || details.stakedAmount === 0n) {
|
||||||
|
return emptyResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or initialize tracking data
|
||||||
|
const trackingData = getStakingTrackingData();
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (!trackingData[address]) {
|
||||||
|
// First time seeing stake - start tracking
|
||||||
|
trackingData[address] = {
|
||||||
|
startTime: now,
|
||||||
|
lastChecked: now,
|
||||||
|
lastStakeAmount: details.stakedAmount.toString()
|
||||||
|
};
|
||||||
|
saveStakingTrackingData(trackingData);
|
||||||
|
} else {
|
||||||
|
// Update last checked time and stake amount
|
||||||
|
trackingData[address].lastChecked = now;
|
||||||
|
trackingData[address].lastStakeAmount = details.stakedAmount.toString();
|
||||||
|
saveStakingTrackingData(trackingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userTracking = trackingData[address];
|
||||||
|
const trackingStarted = new Date(userTracking.startTime);
|
||||||
|
const msStaked = now - userTracking.startTime;
|
||||||
|
const monthsStaked = Math.floor(msStaked / MONTH_IN_MS);
|
||||||
|
|
||||||
|
// Calculate score (matching pallet algorithm)
|
||||||
|
const stakedHez = Number(details.stakedAmount) / UNITS;
|
||||||
|
const baseScore = calculateBaseStakingScore(stakedHez);
|
||||||
|
const timeMultiplier = getStakingTimeMultiplier(monthsStaked);
|
||||||
|
const finalScore = Math.min(Math.floor(baseScore * timeMultiplier), 100);
|
||||||
|
|
||||||
|
// Check if needs refresh (older than 24 hours)
|
||||||
|
const needsRefresh = now - userTracking.lastChecked > DAY_IN_MS;
|
||||||
|
|
||||||
|
return {
|
||||||
|
score: finalScore,
|
||||||
|
stakedAmount: details.stakedAmount,
|
||||||
|
stakedHez,
|
||||||
|
trackingStarted,
|
||||||
|
monthsStaked,
|
||||||
|
timeMultiplier,
|
||||||
|
nominationsCount: details.nominationsCount,
|
||||||
|
isFromFrontend: true,
|
||||||
|
needsRefresh
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get staking score with frontend fallback
|
||||||
|
* First tries on-chain pallet, falls back to frontend calculation if it fails
|
||||||
|
*
|
||||||
|
* @param peopleApi - People Chain API (optional, for on-chain score)
|
||||||
|
* @param relayApi - Relay Chain API (required for stake data)
|
||||||
|
* @param address - User's blockchain address
|
||||||
|
*/
|
||||||
|
export async function getStakingScoreWithFallback(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<FrontendStakingScoreResult> {
|
||||||
|
// First try on-chain score from People Chain
|
||||||
|
if (peopleApi) {
|
||||||
|
try {
|
||||||
|
const status = await getStakingScoreStatus(peopleApi, address);
|
||||||
|
if (status.isTracking && status.startBlock) {
|
||||||
|
// On-chain tracking is active, use it
|
||||||
|
const onChainScore = await getStakingScore(peopleApi, address, relayApi);
|
||||||
|
if (onChainScore > 0) {
|
||||||
|
const details = await fetchRelayStakingDetails(relayApi, address);
|
||||||
|
return {
|
||||||
|
score: onChainScore,
|
||||||
|
stakedAmount: details?.stakedAmount || 0n,
|
||||||
|
stakedHez: details ? Number(details.stakedAmount) / UNITS : 0,
|
||||||
|
trackingStarted: new Date(status.startBlock * 6000), // Approximate
|
||||||
|
monthsStaked: Math.floor(status.durationBlocks / (30 * 24 * 60 * 10)),
|
||||||
|
timeMultiplier: 1.0, // Already applied in on-chain score
|
||||||
|
nominationsCount: details?.nominationsCount || 0,
|
||||||
|
isFromFrontend: false,
|
||||||
|
needsRefresh: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('On-chain staking score not available, using frontend fallback');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to frontend calculation
|
||||||
|
return getFrontendStakingScore(relayApi, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format staked amount for display
|
||||||
|
*/
|
||||||
|
export function formatStakedAmount(amount: bigint): string {
|
||||||
|
const hez = Number(amount) / UNITS;
|
||||||
|
if (hez >= 1000000) return `${(hez / 1000000).toFixed(2)}M`;
|
||||||
|
if (hez >= 1000) return `${(hez / 1000).toFixed(2)}K`;
|
||||||
|
return hez.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// FRONTEND TRUST SCORE (Fallback)
|
||||||
|
// ========================================
|
||||||
|
// Until runtime upgrade, calculate trust score using frontend fallback
|
||||||
|
// Formula from pezpallet-trust:
|
||||||
|
// weighted_sum = staking*100 + referral*300 + perwerde*300 + tiki*300
|
||||||
|
// trust_score = (staking * weighted_sum) / 100
|
||||||
|
|
||||||
|
const SCORE_MULTIPLIER_BASE = 100;
|
||||||
|
|
||||||
|
export interface FrontendTrustScoreResult {
|
||||||
|
trustScore: number;
|
||||||
|
stakingScore: number;
|
||||||
|
referralScore: number;
|
||||||
|
perwerdeScore: number;
|
||||||
|
tikiScore: number;
|
||||||
|
weightedSum: number;
|
||||||
|
isFromFrontend: boolean;
|
||||||
|
isCitizen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is a citizen (KYC approved)
|
||||||
|
*/
|
||||||
|
export async function checkCitizenshipStatus(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
address: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!peopleApi || !address) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!peopleApi.query?.identityKyc?.kycStatuses) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await peopleApi.query.identityKyc.kycStatuses(address);
|
||||||
|
const statusStr = status.toString();
|
||||||
|
|
||||||
|
// KycLevel::Approved = "Approved" or numeric value 3
|
||||||
|
return statusStr === 'Approved' || statusStr === '3';
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error checking citizenship status:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Perwerde (education) score
|
||||||
|
* This is from pezpallet-perwerde on People Chain
|
||||||
|
*/
|
||||||
|
export async function getPerwerdeScore(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
address: string
|
||||||
|
): Promise<number> {
|
||||||
|
if (!peopleApi || !address) return 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if perwerde pallet exists
|
||||||
|
if (!peopleApi.query?.perwerde) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get user's completed courses/certifications
|
||||||
|
// The exact storage depends on pallet implementation
|
||||||
|
if (peopleApi.query.perwerde.userScores) {
|
||||||
|
const score = await peopleApi.query.perwerde.userScores(address);
|
||||||
|
if (!score.isEmpty) {
|
||||||
|
return Number(score.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative: count completed courses
|
||||||
|
if (peopleApi.query.perwerde.completedCourses) {
|
||||||
|
const courses = await peopleApi.query.perwerde.completedCourses(address);
|
||||||
|
const coursesJson = courses.toJSON() as unknown[];
|
||||||
|
if (Array.isArray(coursesJson)) {
|
||||||
|
// Each completed course = 10 points, max 50
|
||||||
|
return Math.min(coursesJson.length * 10, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching perwerde score:', err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate trust score using frontend fallback
|
||||||
|
* Uses the same formula as pezpallet-trust
|
||||||
|
*
|
||||||
|
* @param peopleApi - People Chain API (optional)
|
||||||
|
* @param relayApi - Relay Chain API (required for staking data)
|
||||||
|
* @param address - User's blockchain address
|
||||||
|
*/
|
||||||
|
export async function getFrontendTrustScore(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<FrontendTrustScoreResult> {
|
||||||
|
const emptyResult: FrontendTrustScoreResult = {
|
||||||
|
trustScore: 0,
|
||||||
|
stakingScore: 0,
|
||||||
|
referralScore: 0,
|
||||||
|
perwerdeScore: 0,
|
||||||
|
tikiScore: 0,
|
||||||
|
weightedSum: 0,
|
||||||
|
isFromFrontend: true,
|
||||||
|
isCitizen: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!address) return emptyResult;
|
||||||
|
|
||||||
|
// Check citizenship status
|
||||||
|
const isCitizen = await checkCitizenshipStatus(peopleApi, address);
|
||||||
|
|
||||||
|
// Get component scores in parallel
|
||||||
|
const [stakingResult, referralScore, perwerdeScore, tikiScore] = await Promise.all([
|
||||||
|
getFrontendStakingScore(relayApi, address),
|
||||||
|
peopleApi ? getReferralScore(peopleApi, address) : Promise.resolve(0),
|
||||||
|
getPerwerdeScore(peopleApi, address),
|
||||||
|
peopleApi ? getTikiScore(peopleApi, address) : Promise.resolve(0)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stakingScore = stakingResult.score;
|
||||||
|
|
||||||
|
// If staking score is 0, trust score is 0 (matches pallet logic)
|
||||||
|
if (stakingScore === 0) {
|
||||||
|
return {
|
||||||
|
...emptyResult,
|
||||||
|
referralScore,
|
||||||
|
perwerdeScore,
|
||||||
|
tikiScore,
|
||||||
|
isCitizen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate weighted sum (matching pallet formula)
|
||||||
|
const weightedSum =
|
||||||
|
stakingScore * 100 +
|
||||||
|
referralScore * 300 +
|
||||||
|
perwerdeScore * 300 +
|
||||||
|
tikiScore * 300;
|
||||||
|
|
||||||
|
// Calculate final trust score
|
||||||
|
// trust_score = (staking * weighted_sum) / 100
|
||||||
|
const trustScore = Math.floor((stakingScore * weightedSum) / SCORE_MULTIPLIER_BASE);
|
||||||
|
|
||||||
|
return {
|
||||||
|
trustScore,
|
||||||
|
stakingScore,
|
||||||
|
referralScore,
|
||||||
|
perwerdeScore,
|
||||||
|
tikiScore,
|
||||||
|
weightedSum,
|
||||||
|
isFromFrontend: true,
|
||||||
|
isCitizen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get trust score with frontend fallback
|
||||||
|
* First tries on-chain, falls back to frontend calculation
|
||||||
|
*/
|
||||||
|
export async function getTrustScoreWithFallback(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<FrontendTrustScoreResult> {
|
||||||
|
// First try on-chain trust score
|
||||||
|
if (peopleApi) {
|
||||||
|
try {
|
||||||
|
const onChainScore = await getTrustScore(peopleApi, address);
|
||||||
|
if (onChainScore > 0) {
|
||||||
|
// Get component scores for display
|
||||||
|
const [stakingResult, referralScore, perwerdeScore, tikiScore] = await Promise.all([
|
||||||
|
getFrontendStakingScore(relayApi, address),
|
||||||
|
getReferralScore(peopleApi, address),
|
||||||
|
getPerwerdeScore(peopleApi, address),
|
||||||
|
getTikiScore(peopleApi, address)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isCitizen = await checkCitizenshipStatus(peopleApi, address);
|
||||||
|
|
||||||
|
return {
|
||||||
|
trustScore: onChainScore,
|
||||||
|
stakingScore: stakingResult.score,
|
||||||
|
referralScore,
|
||||||
|
perwerdeScore,
|
||||||
|
tikiScore,
|
||||||
|
weightedSum: 0, // Not calculated for on-chain
|
||||||
|
isFromFrontend: false,
|
||||||
|
isCitizen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('On-chain trust score not available, using frontend fallback');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to frontend calculation
|
||||||
|
return getFrontendTrustScore(peopleApi, relayApi, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all scores with frontend fallback for staking and trust
|
||||||
|
*/
|
||||||
|
export async function getAllScoresWithFallback(
|
||||||
|
peopleApi: ApiPromise | null,
|
||||||
|
relayApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<UserScores & { isFromFrontend: boolean }> {
|
||||||
|
if (!address) {
|
||||||
|
return {
|
||||||
|
trustScore: 0,
|
||||||
|
referralScore: 0,
|
||||||
|
stakingScore: 0,
|
||||||
|
tikiScore: 0,
|
||||||
|
totalScore: 0,
|
||||||
|
isFromFrontend: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const trustResult = await getTrustScoreWithFallback(peopleApi, relayApi, address);
|
||||||
|
|
||||||
|
return {
|
||||||
|
trustScore: trustResult.trustScore,
|
||||||
|
referralScore: trustResult.referralScore,
|
||||||
|
stakingScore: trustResult.stakingScore,
|
||||||
|
tikiScore: trustResult.tikiScore,
|
||||||
|
totalScore: trustResult.trustScore, // Trust score IS the total score
|
||||||
|
isFromFrontend: trustResult.isFromFrontend
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AddTokenModal } from './AddTokenModal';
|
|||||||
import { TransferModal } from './TransferModal';
|
import { TransferModal } from './TransferModal';
|
||||||
import { XCMTeleportModal } from './XCMTeleportModal';
|
import { XCMTeleportModal } from './XCMTeleportModal';
|
||||||
import { LPStakeModal } from './LPStakeModal';
|
import { LPStakeModal } from './LPStakeModal';
|
||||||
import { getAllScores, type UserScores } from '@pezkuwi/lib/scores';
|
import { getAllScoresWithFallback, type UserScores } from '@pezkuwi/lib/scores';
|
||||||
|
|
||||||
interface TokenBalance {
|
interface TokenBalance {
|
||||||
assetId: number;
|
assetId: number;
|
||||||
@@ -554,7 +554,7 @@ export const AccountBalance: React.FC = () => {
|
|||||||
fetchBalance();
|
fetchBalance();
|
||||||
fetchTokenPrices(); // Fetch token USD prices from pools
|
fetchTokenPrices(); // Fetch token USD prices from pools
|
||||||
|
|
||||||
// Fetch All Scores from blockchain
|
// Fetch All Scores from blockchain with frontend fallback
|
||||||
const fetchAllScores = async () => {
|
const fetchAllScores = async () => {
|
||||||
if (!api || !isApiReady || !selectedAccount?.address) {
|
if (!api || !isApiReady || !selectedAccount?.address) {
|
||||||
setScores({
|
setScores({
|
||||||
@@ -569,7 +569,12 @@ export const AccountBalance: React.FC = () => {
|
|||||||
|
|
||||||
setLoadingScores(true);
|
setLoadingScores(true);
|
||||||
try {
|
try {
|
||||||
const userScores = await getAllScores(api, selectedAccount.address);
|
// Use fallback function: peopleApi for on-chain scores, api (Relay) for staking data
|
||||||
|
const userScores = await getAllScoresWithFallback(
|
||||||
|
peopleApi || null, // People Chain for referral, tiki, perwerde
|
||||||
|
api, // Relay Chain for staking data
|
||||||
|
selectedAccount.address
|
||||||
|
);
|
||||||
setScores(userScores);
|
setScores(userScores);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (import.meta.env.DEV) console.error('Failed to fetch scores:', err);
|
if (import.meta.env.DEV) console.error('Failed to fetch scores:', err);
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import { ChevronRight, Shield } from 'lucide-react';
|
|||||||
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
||||||
import { useWallet } from '../contexts/WalletContext'; // Import useWallet
|
import { useWallet } from '../contexts/WalletContext'; // Import useWallet
|
||||||
import { formatBalance } from '@pezkuwi/lib/wallet';
|
import { formatBalance } from '@pezkuwi/lib/wallet';
|
||||||
|
import { getTrustScoreWithFallback } from '@pezkuwi/lib/scores';
|
||||||
|
|
||||||
const HeroSection: React.FC = () => {
|
const HeroSection: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { api, isApiReady } = usePezkuwi();
|
const { api, isApiReady, peopleApi } = usePezkuwi();
|
||||||
const { selectedAccount } = useWallet(); // Use selectedAccount from WalletContext
|
const { selectedAccount } = useWallet(); // Use selectedAccount from WalletContext
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
activeProposals: 0,
|
activeProposals: 0,
|
||||||
@@ -23,11 +24,13 @@ const HeroSection: React.FC = () => {
|
|||||||
let currentTrustScore = 0; // Default if not fetched or no account
|
let currentTrustScore = 0; // Default if not fetched or no account
|
||||||
if (selectedAccount?.address) {
|
if (selectedAccount?.address) {
|
||||||
try {
|
try {
|
||||||
// Assuming pallet-staking-score has a storage item for trust scores
|
// Use frontend fallback for trust score
|
||||||
// The exact query might need adjustment based on chain metadata
|
const trustResult = await getTrustScoreWithFallback(
|
||||||
const rawTrustScore = await api.query.stakingScore.trustScore(selectedAccount.address);
|
peopleApi || null,
|
||||||
// Assuming trustScore is a simple number or a wrapper around it
|
api,
|
||||||
currentTrustScore = rawTrustScore.isSome ? rawTrustScore.unwrap().toNumber() : 0;
|
selectedAccount.address
|
||||||
|
);
|
||||||
|
currentTrustScore = trustResult.trustScore;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (import.meta.env.DEV) console.warn('Failed to fetch trust score:', err);
|
if (import.meta.env.DEV) console.warn('Failed to fetch trust score:', err);
|
||||||
currentTrustScore = 0;
|
currentTrustScore = 0;
|
||||||
@@ -91,7 +94,7 @@ const HeroSection: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchStats();
|
fetchStats();
|
||||||
}, [api, isApiReady, selectedAccount]); // Add selectedAccount to dependencies
|
}, [api, isApiReady, peopleApi, selectedAccount]); // Add peopleApi to dependencies
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative min-h-screen flex items-center justify-start overflow-hidden bg-gray-950">
|
<section className="relative min-h-screen flex items-center justify-start overflow-hidden bg-gray-950">
|
||||||
|
|||||||
Reference in New Issue
Block a user