Files
pwap/shared/lib/scores.ts
T
pezkuwichain 02094a3635 feat: add XCM teleport and CI/CD deployment workflow
Features:
- Add XCMTeleportModal for cross-chain HEZ transfers
- Support Asset Hub and People Chain teleports
- Add "Fund Fees" button with user-friendly tooltips
- Use correct XCM V3 format with teyrchain junction

Fixes:
- Fix PEZ transfer to use Asset Hub API
- Silence unnecessary pallet availability warnings
- Fix transaction loading performance (10 blocks limit)
- Remove Supabase admin_roles dependency

CI/CD:
- Add auto-deploy to VPS on main branch push
- Add version bumping on deploy
- Upload build artifacts for deployment
2026-02-04 11:35:25 +03:00

367 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ========================================
// Score Systems Integration
// ========================================
// Centralized score fetching from blockchain pallets
import type { ApiPromise } from '@pezkuwi/api';
// ========================================
// TYPE DEFINITIONS
// ========================================
export interface UserScores {
trustScore: number;
referralScore: number;
stakingScore: number;
tikiScore: number;
totalScore: number;
}
export interface TrustScoreDetails {
totalScore: number;
stakingPoints: number;
referralPoints: number;
tikiPoints: number;
activityPoints: number;
historyLength: number;
}
// ========================================
// TRUST SCORE (pallet_trust)
// ========================================
/**
* Fetch user's trust score from blockchain
* pallet_trust::TrustScores storage
*/
export async function getTrustScore(
api: ApiPromise,
address: string
): Promise<number> {
try {
if (!api?.query?.trust) {
// Trust pallet not available on this chain - this is expected
return 0;
}
const score = await api.query.trust.trustScores(address);
if (score.isEmpty) {
return 0;
}
return Number(score.toString());
} catch (error) {
console.error('Error fetching trust score:', error);
return 0;
}
}
/**
* Fetch detailed trust score breakdown
* pallet_trust::ScoreHistory storage
*/
export async function getTrustScoreDetails(
api: ApiPromise,
address: string
): Promise<TrustScoreDetails | null> {
try {
if (!api?.query?.trust) {
return null;
}
const totalScore = await getTrustScore(api, address);
// Get score history to show detailed breakdown
const historyResult = await api.query.trust.scoreHistory(address);
if (historyResult.isEmpty) {
return {
totalScore,
stakingPoints: 0,
referralPoints: 0,
tikiPoints: 0,
activityPoints: 0,
historyLength: 0
};
}
const history = historyResult.toJSON() as any[];
// Calculate points from history
// History format: [{blockNumber, score, reason}]
let stakingPoints = 0;
let referralPoints = 0;
let tikiPoints = 0;
let activityPoints = 0;
for (const entry of history) {
const reason = entry.reason || '';
const score = entry.score || 0;
if (reason.includes('Staking')) stakingPoints += score;
else if (reason.includes('Referral')) referralPoints += score;
else if (reason.includes('Tiki') || reason.includes('Role')) tikiPoints += score;
else activityPoints += score;
}
return {
totalScore,
stakingPoints,
referralPoints,
tikiPoints,
activityPoints,
historyLength: history.length
};
} catch (error) {
console.error('Error fetching trust score details:', error);
return null;
}
}
// ========================================
// REFERRAL SCORE (pallet_trust)
// ========================================
/**
* Fetch user's referral score
* Reads from pallet_referral::ReferralCount storage
*
* Score calculation based on referral count:
* - 0 referrals: 0 points
* - 1-5 referrals: count × 4 points (4, 8, 12, 16, 20)
* - 6-20 referrals: 20 + (count - 5) × 2 points
* - 21+ referrals: capped at 50 points
*/
export async function getReferralScore(
api: ApiPromise,
address: string
): Promise<number> {
try {
if (!api?.query?.referral?.referralCount) {
if (typeof __DEV__ !== 'undefined' && __DEV__) console.warn('Referral pallet not available');
return 0;
}
const count = await api.query.referral.referralCount(address);
const referralCount = Number(count.toString());
// Calculate score based on referral count
if (referralCount === 0) return 0;
if (referralCount <= 5) return referralCount * 4;
if (referralCount <= 20) return 20 + ((referralCount - 5) * 2);
return 50; // Capped at 50 points
} catch (error) {
console.error('Error fetching referral score:', error);
return 0;
}
}
/**
* Get referral count for user
* pallet_trust::Referrals storage
*/
export async function getReferralCount(
api: ApiPromise,
address: string
): Promise<number> {
try {
if (!api?.query?.trust?.referrals) {
return 0;
}
const referrals = await api.query.trust.referrals(address);
if (referrals.isEmpty) {
return 0;
}
const referralList = referrals.toJSON() as any[];
return Array.isArray(referralList) ? referralList.length : 0;
} catch (error) {
console.error('Error fetching referral count:', error);
return 0;
}
}
// ========================================
// STAKING SCORE (pallet_staking_score)
// ========================================
/**
* Get staking score from pallet_staking_score
* This is already implemented in lib/staking.ts
* Re-exported here for consistency
*/
export async function getStakingScoreFromPallet(
api: ApiPromise,
address: string
): Promise<number> {
try {
if (!api?.query?.stakingScore) {
// Staking score pallet not available on this chain - this is expected
return 0;
}
// Check if user has started score tracking
const scoreResult = await api.query.stakingScore.stakingStartBlock(address);
if (scoreResult.isNone) {
return 0;
}
// Get staking info from staking pallet
const ledger = await api.query.staking.ledger(address);
if (ledger.isNone) {
return 0;
}
const ledgerCodec = ledger.unwrap() as { toJSON: () => unknown };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ledgerData = ledgerCodec.toJSON() as any;
const stakedAmount = Number(ledgerData.total || 0) / 1e12; // Convert to HEZ
// Get duration
const scoreCodec = scoreResult.unwrap() as { toString: () => string };
const startBlock = Number(scoreCodec.toString());
const currentBlock = Number((await api.query.system.number()).toString());
const durationInBlocks = currentBlock - startBlock;
// Calculate score based on amount and duration
// Amount-based score (20-50 points)
let amountScore = 20;
if (stakedAmount <= 100) amountScore = 20;
else if (stakedAmount <= 250) amountScore = 30;
else if (stakedAmount <= 750) amountScore = 40;
else amountScore = 50;
// Duration multiplier
const MONTH_IN_BLOCKS = 30 * 24 * 60 * 10; // ~30 days
let durationMultiplier = 1.0;
if (durationInBlocks >= 12 * MONTH_IN_BLOCKS) durationMultiplier = 2.0;
else if (durationInBlocks >= 6 * MONTH_IN_BLOCKS) durationMultiplier = 1.7;
else if (durationInBlocks >= 3 * MONTH_IN_BLOCKS) durationMultiplier = 1.4;
else if (durationInBlocks >= MONTH_IN_BLOCKS) durationMultiplier = 1.2;
return Math.min(100, Math.floor(amountScore * durationMultiplier));
} catch (error) {
console.error('Error fetching staking score:', error);
return 0;
}
}
// ========================================
// TIKI SCORE (from lib/tiki.ts)
// ========================================
/**
* Calculate Tiki score from user's roles
* Import from lib/tiki.ts
*/
import { fetchUserTikis, calculateTikiScore } from './tiki';
export async function getTikiScore(
api: ApiPromise,
address: string
): Promise<number> {
try {
const tikis = await fetchUserTikis(api, address);
return calculateTikiScore(tikis);
} catch (error) {
console.error('Error fetching tiki score:', error);
return 0;
}
}
// ========================================
// COMPREHENSIVE SCORE FETCHING
// ========================================
/**
* Fetch all scores for a user in one call
*/
export async function getAllScores(
api: ApiPromise,
address: string
): Promise<UserScores> {
try {
if (!api || !address) {
return {
trustScore: 0,
referralScore: 0,
stakingScore: 0,
tikiScore: 0,
totalScore: 0
};
}
// Fetch all scores in parallel
const [trustScore, referralScore, stakingScore, tikiScore] = await Promise.all([
getTrustScore(api, address),
getReferralScore(api, address),
getStakingScoreFromPallet(api, address),
getTikiScore(api, address)
]);
const totalScore = trustScore + referralScore + stakingScore + tikiScore;
return {
trustScore,
referralScore,
stakingScore,
tikiScore,
totalScore
};
} catch (error) {
console.error('Error fetching all scores:', error);
return {
trustScore: 0,
referralScore: 0,
stakingScore: 0,
tikiScore: 0,
totalScore: 0
};
}
}
// ========================================
// SCORE DISPLAY HELPERS
// ========================================
/**
* Get color class based on score
*/
export function getScoreColor(score: number): string {
if (score >= 200) return 'text-purple-500';
if (score >= 150) return 'text-pink-500';
if (score >= 100) return 'text-blue-500';
if (score >= 70) return 'text-cyan-500';
if (score >= 40) return 'text-teal-500';
if (score >= 20) return 'text-green-500';
return 'text-gray-500';
}
/**
* Get score rating label
*/
export function getScoreRating(score: number): string {
if (score >= 250) return 'Legendary';
if (score >= 200) return 'Excellent';
if (score >= 150) return 'Very Good';
if (score >= 100) return 'Good';
if (score >= 70) return 'Average';
if (score >= 40) return 'Fair';
if (score >= 20) return 'Low';
return 'Very Low';
}
/**
* Format score for display
*/
export function formatScore(score: number): string {
return score.toFixed(0);
}