mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-07-20 20:05:40 +00:00
fix: update score system to use correct People Chain APIs
- Remove LP staking score (not available in blockchain) - All scores now fetched from People Chain - Add Start Score Tracking button to Dashboard - Add staking status display showing tracking duration
This commit is contained in:
+161
-244
@@ -1,7 +1,11 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Score Systems Integration
|
// Score Systems Integration
|
||||||
// ========================================
|
// ========================================
|
||||||
// Centralized score fetching from blockchain pallets
|
// All scores come from People Chain (people-rpc.pezkuwichain.io)
|
||||||
|
// - Trust Score: pezpallet-trust
|
||||||
|
// - Referral Score: pezpallet-referral
|
||||||
|
// - Staking Score: pezpallet-staking-score
|
||||||
|
// - Tiki Score: pezpallet-tiki
|
||||||
|
|
||||||
import type { ApiPromise } from '@pezkuwi/api';
|
import type { ApiPromise } from '@pezkuwi/api';
|
||||||
|
|
||||||
@@ -13,39 +17,35 @@ export interface UserScores {
|
|||||||
trustScore: number;
|
trustScore: number;
|
||||||
referralScore: number;
|
referralScore: number;
|
||||||
stakingScore: number;
|
stakingScore: number;
|
||||||
lpStakingScore: number;
|
|
||||||
tikiScore: number;
|
tikiScore: number;
|
||||||
totalScore: number;
|
totalScore: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrustScoreDetails {
|
export interface StakingScoreStatus {
|
||||||
totalScore: number;
|
isTracking: boolean;
|
||||||
stakingPoints: number;
|
startBlock: number | null;
|
||||||
referralPoints: number;
|
currentBlock: number;
|
||||||
tikiPoints: number;
|
durationBlocks: number;
|
||||||
activityPoints: number;
|
|
||||||
historyLength: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TRUST SCORE (pallet_trust)
|
// TRUST SCORE (pezpallet-trust on People Chain)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch user's trust score from blockchain
|
* Fetch user's trust score from blockchain
|
||||||
* pallet_trust::TrustScores storage
|
* Storage: trust.trustScores(address)
|
||||||
*/
|
*/
|
||||||
export async function getTrustScore(
|
export async function getTrustScore(
|
||||||
api: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string
|
address: string
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
if (!api?.query?.trust) {
|
if (!peopleApi?.query?.trust?.trustScores) {
|
||||||
// Trust pallet not available on this chain - this is expected
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const score = await api.query.trust.trustScores(address);
|
const score = await peopleApi.query.trust.trustScores(address);
|
||||||
|
|
||||||
if (score.isEmpty) {
|
if (score.isEmpty) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -58,101 +58,36 @@ export async function getTrustScore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
// REFERRAL SCORE (pezpallet-referral on People Chain)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch user's referral score
|
* Fetch user's referral count and calculate score
|
||||||
* Reads from pallet_referral::ReferralCount storage
|
* Storage: referral.referralCount(address)
|
||||||
*
|
*
|
||||||
* Score calculation based on referral count:
|
* Score calculation:
|
||||||
* - 0 referrals: 0 points
|
* - 0 referrals: 0 points
|
||||||
* - 1-5 referrals: count × 4 points (4, 8, 12, 16, 20)
|
* - 1-5 referrals: count × 4 points
|
||||||
* - 6-20 referrals: 20 + (count - 5) × 2 points
|
* - 6-20 referrals: 20 + (count - 5) × 2 points
|
||||||
* - 21+ referrals: capped at 50 points
|
* - 21+ referrals: capped at 50 points
|
||||||
*/
|
*/
|
||||||
export async function getReferralScore(
|
export async function getReferralScore(
|
||||||
api: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string
|
address: string
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
if (!api?.query?.referral?.referralCount) {
|
if (!peopleApi?.query?.referral?.referralCount) {
|
||||||
if (typeof __DEV__ !== 'undefined' && __DEV__) console.warn('Referral pallet not available');
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await api.query.referral.referralCount(address);
|
const count = await peopleApi.query.referral.referralCount(address);
|
||||||
const referralCount = Number(count.toString());
|
const referralCount = Number(count.toString());
|
||||||
|
|
||||||
// Calculate score based on referral count
|
|
||||||
if (referralCount === 0) return 0;
|
if (referralCount === 0) return 0;
|
||||||
if (referralCount <= 5) return referralCount * 4;
|
if (referralCount <= 5) return referralCount * 4;
|
||||||
if (referralCount <= 20) return 20 + ((referralCount - 5) * 2);
|
if (referralCount <= 20) return 20 + ((referralCount - 5) * 2);
|
||||||
return 50; // Capped at 50 points
|
return 50; // Capped at 50 points
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching referral score:', error);
|
console.error('Error fetching referral score:', error);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -160,26 +95,19 @@ export async function getReferralScore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get referral count for user
|
* Get raw referral count
|
||||||
* pallet_trust::Referrals storage
|
|
||||||
*/
|
*/
|
||||||
export async function getReferralCount(
|
export async function getReferralCount(
|
||||||
api: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string
|
address: string
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
if (!api?.query?.trust?.referrals) {
|
if (!peopleApi?.query?.referral?.referralCount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const referrals = await api.query.trust.referrals(address);
|
const count = await peopleApi.query.referral.referralCount(address);
|
||||||
|
return Number(count.toString());
|
||||||
if (referrals.isEmpty) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const referralList = referrals.toJSON() as any[];
|
|
||||||
return Array.isArray(referralList) ? referralList.length : 0;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching referral count:', error);
|
console.error('Error fetching referral count:', error);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -187,86 +115,103 @@ export async function getReferralCount(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// STAKING SCORE (pallet_staking_score)
|
// STAKING SCORE (pezpallet-staking-score on People Chain)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get staking score from pallet_staking_score
|
* Check staking score tracking status
|
||||||
* NOTE: stakingScore pallet is on People Chain, but staking.ledger is on Relay Chain
|
* Storage: stakingScore.stakingStartBlock(address)
|
||||||
* So this function needs both APIs
|
|
||||||
*
|
|
||||||
* @param peopleApi - API for People Chain (stakingScore pallet)
|
|
||||||
* @param relayApi - API for Relay Chain (staking pallet) - optional, if not provided uses peopleApi
|
|
||||||
*/
|
*/
|
||||||
export async function getStakingScoreFromPallet(
|
export async function getStakingScoreStatus(
|
||||||
|
peopleApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<StakingScoreStatus> {
|
||||||
|
try {
|
||||||
|
if (!peopleApi?.query?.stakingScore?.stakingStartBlock) {
|
||||||
|
return { isTracking: false, startBlock: null, currentBlock: 0, durationBlocks: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
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, startBlock: null, currentBlock, durationBlocks: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const startBlock = Number(startBlockResult.toString());
|
||||||
|
const durationBlocks = currentBlock - startBlock;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isTracking: true,
|
||||||
|
startBlock,
|
||||||
|
currentBlock,
|
||||||
|
durationBlocks
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching staking score status:', error);
|
||||||
|
return { isTracking: false, startBlock: null, currentBlock: 0, durationBlocks: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get staking score from pallet
|
||||||
|
* Requires: User must have called startScoreTracking() first
|
||||||
|
* Score based on staking duration + amount staked on Relay Chain
|
||||||
|
*
|
||||||
|
* @param peopleApi - People Chain API (stakingScore pallet)
|
||||||
|
* @param address - User's blockchain address
|
||||||
|
* @param relayApi - Relay Chain API (staking.ledger for staked amount)
|
||||||
|
*/
|
||||||
|
export async function getStakingScore(
|
||||||
peopleApi: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string,
|
address: string,
|
||||||
relayApi?: ApiPromise
|
relayApi?: ApiPromise
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
if (!peopleApi?.query?.stakingScore) {
|
const status = await getStakingScoreStatus(peopleApi, address);
|
||||||
// Staking score pallet not available on this chain
|
|
||||||
return 0;
|
if (!status.isTracking) {
|
||||||
|
return 0; // User hasn't started score tracking
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has started score tracking (People Chain)
|
// Get staked amount from Relay Chain if available
|
||||||
const scoreResult = await peopleApi.query.stakingScore.stakingStartBlock(address);
|
let stakedAmount = 0;
|
||||||
|
if (relayApi?.query?.staking?.ledger) {
|
||||||
if (scoreResult.isNone) {
|
const ledger = await relayApi.query.staking.ledger(address);
|
||||||
return 0;
|
if (!ledger.isEmpty && !ledger.isNone) {
|
||||||
|
const ledgerData = (ledger as any).unwrap().toJSON();
|
||||||
|
stakedAmount = Number(ledgerData.total || 0) / 1e12;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get staking info from staking pallet (Relay Chain)
|
// Calculate score based on amount
|
||||||
const stakingApi = relayApi || peopleApi;
|
// Amount-based score (0-50 points)
|
||||||
if (!stakingApi?.query?.staking?.ledger) {
|
let amountScore = 0;
|
||||||
// Staking pallet not available - can't calculate full score
|
if (stakedAmount > 0) {
|
||||||
// Return base score from duration only
|
if (stakedAmount <= 10) amountScore = 10;
|
||||||
const scoreCodec = scoreResult.unwrap() as { toString: () => string };
|
else if (stakedAmount <= 100) amountScore = 20;
|
||||||
const startBlock = Number(scoreCodec.toString());
|
else if (stakedAmount <= 250) amountScore = 30;
|
||||||
const currentBlock = Number((await peopleApi.query.system.number()).toString());
|
else if (stakedAmount <= 750) amountScore = 40;
|
||||||
const durationInBlocks = currentBlock - startBlock;
|
else amountScore = 50;
|
||||||
|
|
||||||
const MONTH_IN_BLOCKS = 30 * 24 * 60 * 10;
|
|
||||||
if (durationInBlocks >= 12 * MONTH_IN_BLOCKS) return 40;
|
|
||||||
if (durationInBlocks >= 6 * MONTH_IN_BLOCKS) return 34;
|
|
||||||
if (durationInBlocks >= 3 * MONTH_IN_BLOCKS) return 28;
|
|
||||||
if (durationInBlocks >= MONTH_IN_BLOCKS) return 24;
|
|
||||||
return 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ledger = await stakingApi.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 from People Chain
|
|
||||||
const scoreCodec = scoreResult.unwrap() as { toString: () => string };
|
|
||||||
const startBlock = Number(scoreCodec.toString());
|
|
||||||
const currentBlock = Number((await peopleApi.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
|
// Duration multiplier
|
||||||
const MONTH_IN_BLOCKS = 30 * 24 * 60 * 10; // ~30 days
|
const MONTH_IN_BLOCKS = 30 * 24 * 60 * 10; // ~432000 blocks per month
|
||||||
let durationMultiplier = 1.0;
|
let durationMultiplier = 1.0;
|
||||||
|
|
||||||
if (durationInBlocks >= 12 * MONTH_IN_BLOCKS) durationMultiplier = 2.0;
|
if (status.durationBlocks >= 12 * MONTH_IN_BLOCKS) durationMultiplier = 2.0;
|
||||||
else if (durationInBlocks >= 6 * MONTH_IN_BLOCKS) durationMultiplier = 1.7;
|
else if (status.durationBlocks >= 6 * MONTH_IN_BLOCKS) durationMultiplier = 1.7;
|
||||||
else if (durationInBlocks >= 3 * MONTH_IN_BLOCKS) durationMultiplier = 1.4;
|
else if (status.durationBlocks >= 3 * MONTH_IN_BLOCKS) durationMultiplier = 1.4;
|
||||||
else if (durationInBlocks >= MONTH_IN_BLOCKS) durationMultiplier = 1.2;
|
else if (status.durationBlocks >= MONTH_IN_BLOCKS) durationMultiplier = 1.2;
|
||||||
|
|
||||||
|
// If no staking amount but tracking is active, give base points for duration
|
||||||
|
if (amountScore === 0 && status.isTracking) {
|
||||||
|
if (status.durationBlocks >= 12 * MONTH_IN_BLOCKS) return 20;
|
||||||
|
if (status.durationBlocks >= 6 * MONTH_IN_BLOCKS) return 15;
|
||||||
|
if (status.durationBlocks >= 3 * MONTH_IN_BLOCKS) return 10;
|
||||||
|
if (status.durationBlocks >= MONTH_IN_BLOCKS) return 5;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return Math.min(100, Math.floor(amountScore * durationMultiplier));
|
return Math.min(100, Math.floor(amountScore * durationMultiplier));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -275,83 +220,59 @@ export async function getStakingScoreFromPallet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// LP STAKING SCORE (Asset Hub assetRewards pallet)
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get LP staking score from Asset Hub
|
* Start staking score tracking
|
||||||
* Based on total LP tokens staked across all pools
|
* Calls: stakingScore.startScoreTracking()
|
||||||
*
|
|
||||||
* @param assetHubApi - API for Asset Hub (assetRewards pallet)
|
|
||||||
* @param address - User's blockchain address
|
|
||||||
*/
|
*/
|
||||||
export async function getLpStakingScore(
|
export async function startScoreTracking(
|
||||||
assetHubApi: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string
|
address: string,
|
||||||
): Promise<number> {
|
signer: any
|
||||||
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
try {
|
try {
|
||||||
if (!assetHubApi?.query?.assetRewards?.poolStakers) {
|
if (!peopleApi?.tx?.stakingScore?.startScoreTracking) {
|
||||||
return 0;
|
return { success: false, error: 'stakingScore pallet not available' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query all staking pool entries
|
const tx = peopleApi.tx.stakingScore.startScoreTracking();
|
||||||
const poolEntries = await assetHubApi.query.assetRewards.pools.entries();
|
|
||||||
|
|
||||||
let totalStaked = BigInt(0);
|
return new Promise((resolve) => {
|
||||||
|
tx.signAndSend(address, { signer }, ({ status, dispatchError }) => {
|
||||||
for (const [key] of poolEntries) {
|
if (status.isInBlock || status.isFinalized) {
|
||||||
const poolId = parseInt(key.args[0].toString());
|
if (dispatchError) {
|
||||||
|
if (dispatchError.isModule) {
|
||||||
try {
|
const decoded = peopleApi.registry.findMetaError(dispatchError.asModule);
|
||||||
const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, address]);
|
resolve({ success: false, error: `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}` });
|
||||||
if (stakeInfo && (stakeInfo as { isSome: boolean }).isSome) {
|
} else {
|
||||||
const stakeData = (stakeInfo as { unwrap: () => { toJSON: () => { amount: string } } }).unwrap().toJSON();
|
resolve({ success: false, error: dispatchError.toString() });
|
||||||
totalStaked += BigInt(stakeData.amount || '0');
|
}
|
||||||
|
} else {
|
||||||
|
resolve({ success: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
});
|
||||||
// Skip this pool on error
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to human readable (12 decimals for LP tokens)
|
|
||||||
const stakedAmount = Number(totalStaked) / 1e12;
|
|
||||||
|
|
||||||
// Calculate score based on amount staked
|
|
||||||
// 0-10 LP: 0 points
|
|
||||||
// 10-50 LP: 10 points
|
|
||||||
// 50-100 LP: 20 points
|
|
||||||
// 100-500 LP: 30 points
|
|
||||||
// 500-1000 LP: 40 points
|
|
||||||
// 1000+ LP: 50 points
|
|
||||||
if (stakedAmount < 10) return 0;
|
|
||||||
if (stakedAmount < 50) return 10;
|
|
||||||
if (stakedAmount < 100) return 20;
|
|
||||||
if (stakedAmount < 500) return 30;
|
|
||||||
if (stakedAmount < 1000) return 40;
|
|
||||||
return 50;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching LP staking score:', error);
|
console.error('Error starting score tracking:', error);
|
||||||
return 0;
|
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TIKI SCORE (from lib/tiki.ts)
|
// TIKI SCORE (pezpallet-tiki on People Chain)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate Tiki score from user's roles
|
|
||||||
* Import from lib/tiki.ts
|
|
||||||
*/
|
|
||||||
import { fetchUserTikis, calculateTikiScore } from './tiki';
|
import { fetchUserTikis, calculateTikiScore } from './tiki';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tiki score from user's roles
|
||||||
|
*/
|
||||||
export async function getTikiScore(
|
export async function getTikiScore(
|
||||||
api: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string
|
address: string
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const tikis = await fetchUserTikis(api, address);
|
const tikis = await fetchUserTikis(peopleApi, address);
|
||||||
return calculateTikiScore(tikis);
|
return calculateTikiScore(tikis);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching tiki score:', error);
|
console.error('Error fetching tiki score:', error);
|
||||||
@@ -365,17 +286,16 @@ 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)
|
||||||
*
|
*
|
||||||
* @param peopleApi - API for People Chain (trust, referral, tiki, stakingScore 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 API for Relay Chain (staking pallet for validator staking score)
|
* @param relayApi - Optional Relay Chain API (for staking.ledger amount)
|
||||||
* @param assetHubApi - Optional API for Asset Hub (assetRewards pallet for LP staking score)
|
|
||||||
*/
|
*/
|
||||||
export async function getAllScores(
|
export async function getAllScores(
|
||||||
peopleApi: ApiPromise,
|
peopleApi: ApiPromise,
|
||||||
address: string,
|
address: string,
|
||||||
relayApi?: ApiPromise,
|
relayApi?: ApiPromise
|
||||||
assetHubApi?: ApiPromise
|
|
||||||
): Promise<UserScores> {
|
): Promise<UserScores> {
|
||||||
try {
|
try {
|
||||||
if (!peopleApi || !address) {
|
if (!peopleApi || !address) {
|
||||||
@@ -383,31 +303,25 @@ export async function getAllScores(
|
|||||||
trustScore: 0,
|
trustScore: 0,
|
||||||
referralScore: 0,
|
referralScore: 0,
|
||||||
stakingScore: 0,
|
stakingScore: 0,
|
||||||
lpStakingScore: 0,
|
|
||||||
tikiScore: 0,
|
tikiScore: 0,
|
||||||
totalScore: 0
|
totalScore: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all scores in parallel
|
// Fetch all scores in parallel from People Chain
|
||||||
// - Trust, referral, tiki scores: People Chain
|
const [trustScore, referralScore, stakingScore, tikiScore] = await Promise.all([
|
||||||
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
|
||||||
// - LP Staking score: Asset Hub (assetRewards pallet)
|
|
||||||
const [trustScore, referralScore, stakingScore, lpStakingScore, tikiScore] = await Promise.all([
|
|
||||||
getTrustScore(peopleApi, address),
|
getTrustScore(peopleApi, address),
|
||||||
getReferralScore(peopleApi, address),
|
getReferralScore(peopleApi, address),
|
||||||
getStakingScoreFromPallet(peopleApi, address, relayApi),
|
getStakingScore(peopleApi, address, relayApi),
|
||||||
assetHubApi ? getLpStakingScore(assetHubApi, address) : Promise.resolve(0),
|
|
||||||
getTikiScore(peopleApi, address)
|
getTikiScore(peopleApi, address)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const totalScore = trustScore + referralScore + stakingScore + lpStakingScore + tikiScore;
|
const totalScore = trustScore + referralScore + stakingScore + tikiScore;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trustScore,
|
trustScore,
|
||||||
referralScore,
|
referralScore,
|
||||||
stakingScore,
|
stakingScore,
|
||||||
lpStakingScore,
|
|
||||||
tikiScore,
|
tikiScore,
|
||||||
totalScore
|
totalScore
|
||||||
};
|
};
|
||||||
@@ -417,7 +331,6 @@ export async function getAllScores(
|
|||||||
trustScore: 0,
|
trustScore: 0,
|
||||||
referralScore: 0,
|
referralScore: 0,
|
||||||
stakingScore: 0,
|
stakingScore: 0,
|
||||||
lpStakingScore: 0,
|
|
||||||
tikiScore: 0,
|
tikiScore: 0,
|
||||||
totalScore: 0
|
totalScore: 0
|
||||||
};
|
};
|
||||||
@@ -428,9 +341,6 @@ export async function getAllScores(
|
|||||||
// SCORE DISPLAY HELPERS
|
// SCORE DISPLAY HELPERS
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Get color class based on score
|
|
||||||
*/
|
|
||||||
export function getScoreColor(score: number): string {
|
export function getScoreColor(score: number): string {
|
||||||
if (score >= 200) return 'text-purple-500';
|
if (score >= 200) return 'text-purple-500';
|
||||||
if (score >= 150) return 'text-pink-500';
|
if (score >= 150) return 'text-pink-500';
|
||||||
@@ -441,9 +351,6 @@ export function getScoreColor(score: number): string {
|
|||||||
return 'text-gray-500';
|
return 'text-gray-500';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get score rating label
|
|
||||||
*/
|
|
||||||
export function getScoreRating(score: number): string {
|
export function getScoreRating(score: number): string {
|
||||||
if (score >= 250) return 'Legendary';
|
if (score >= 250) return 'Legendary';
|
||||||
if (score >= 200) return 'Excellent';
|
if (score >= 200) return 'Excellent';
|
||||||
@@ -455,9 +362,19 @@ export function getScoreRating(score: number): string {
|
|||||||
return 'Very Low';
|
return 'Very Low';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format score for display
|
|
||||||
*/
|
|
||||||
export function formatScore(score: number): string {
|
export function formatScore(score: number): string {
|
||||||
return score.toFixed(0);
|
return score.toFixed(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDuration(blocks: number): string {
|
||||||
|
const BLOCKS_PER_MINUTE = 10;
|
||||||
|
const minutes = blocks / BLOCKS_PER_MINUTE;
|
||||||
|
const hours = minutes / 60;
|
||||||
|
const days = hours / 24;
|
||||||
|
const months = days / 30;
|
||||||
|
|
||||||
|
if (months >= 1) return `${Math.floor(months)} month${Math.floor(months) > 1 ? 's' : ''}`;
|
||||||
|
if (days >= 1) return `${Math.floor(days)} day${Math.floor(days) > 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' : ''}`;
|
||||||
|
}
|
||||||
|
|||||||
+89
-32
@@ -7,10 +7,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus, Coins } from 'lucide-react';
|
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus, Play, Loader2 } from 'lucide-react';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki';
|
import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki';
|
||||||
import { getAllScores, type UserScores } from '@pezkuwi/lib/scores';
|
import { getAllScores, getStakingScoreStatus, startScoreTracking, type UserScores, type StakingScoreStatus, formatDuration } from '@pezkuwi/lib/scores';
|
||||||
|
import { web3FromAddress } from '@pezkuwi/extension-dapp';
|
||||||
import { getKycStatus } from '@pezkuwi/lib/kyc';
|
import { getKycStatus } from '@pezkuwi/lib/kyc';
|
||||||
import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
|
import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
|
||||||
// Commission proposals card removed - no longer using notary system for KYC approval
|
// Commission proposals card removed - no longer using notary system for KYC approval
|
||||||
@@ -18,7 +19,7 @@ import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
|
|||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, selectedAccount } = usePezkuwi();
|
const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [profile, setProfile] = useState<Record<string, unknown> | null>(null);
|
const [profile, setProfile] = useState<Record<string, unknown> | null>(null);
|
||||||
@@ -28,11 +29,12 @@ export default function Dashboard() {
|
|||||||
trustScore: 0,
|
trustScore: 0,
|
||||||
referralScore: 0,
|
referralScore: 0,
|
||||||
stakingScore: 0,
|
stakingScore: 0,
|
||||||
lpStakingScore: 0,
|
|
||||||
tikiScore: 0,
|
tikiScore: 0,
|
||||||
totalScore: 0
|
totalScore: 0
|
||||||
});
|
});
|
||||||
const [loadingScores, setLoadingScores] = useState(false);
|
const [loadingScores, setLoadingScores] = useState(false);
|
||||||
|
const [stakingStatus, setStakingStatus] = useState<StakingScoreStatus | null>(null);
|
||||||
|
const [startingScoreTracking, setStartingScoreTracking] = useState(false);
|
||||||
const [kycStatus, setKycStatus] = useState<string>('NotStarted');
|
const [kycStatus, setKycStatus] = useState<string>('NotStarted');
|
||||||
const [renouncingCitizenship, setRenouncingCitizenship] = useState(false);
|
const [renouncingCitizenship, setRenouncingCitizenship] = useState(false);
|
||||||
const [nftDetails, setNftDetails] = useState<{ citizenNFT: TikiNFTDetails | null; roleNFTs: TikiNFTDetails[]; totalNFTs: number }>({
|
const [nftDetails, setNftDetails] = useState<{ citizenNFT: TikiNFTDetails | null; roleNFTs: TikiNFTDetails[]; totalNFTs: number }>({
|
||||||
@@ -106,13 +108,16 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
setLoadingScores(true);
|
setLoadingScores(true);
|
||||||
try {
|
try {
|
||||||
// Fetch all scores from blockchain (includes trust, referral, staking, lpStaking, tiki)
|
// Fetch all scores from blockchain (includes trust, referral, staking, tiki)
|
||||||
// - Trust, referral, tiki: People Chain
|
// - Trust, referral, tiki, stakingScore: People Chain
|
||||||
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
// - Staking amount: Relay Chain (staking.ledger)
|
||||||
// - LP Staking score: Asset Hub (assetRewards pallet)
|
const allScores = await getAllScores(peopleApi, selectedAccount.address, api);
|
||||||
const allScores = await getAllScores(peopleApi, selectedAccount.address, api, assetHubApi || undefined);
|
|
||||||
setScores(allScores);
|
setScores(allScores);
|
||||||
|
|
||||||
|
// Fetch staking score tracking status
|
||||||
|
const stakingStatusResult = await getStakingScoreStatus(peopleApi, selectedAccount.address);
|
||||||
|
setStakingStatus(stakingStatusResult);
|
||||||
|
|
||||||
// Fetch tikis from People Chain (tiki pallet is on People Chain)
|
// Fetch tikis from People Chain (tiki pallet is on People Chain)
|
||||||
const userTikis = await fetchUserTikis(peopleApi, selectedAccount.address);
|
const userTikis = await fetchUserTikis(peopleApi, selectedAccount.address);
|
||||||
setTikis(userTikis);
|
setTikis(userTikis);
|
||||||
@@ -122,21 +127,62 @@ export default function Dashboard() {
|
|||||||
setNftDetails(details);
|
setNftDetails(details);
|
||||||
|
|
||||||
// Fetch KYC status from People Chain (identityKyc pallet is on People Chain)
|
// Fetch KYC status from People Chain (identityKyc pallet is on People Chain)
|
||||||
const status = await getKycStatus(peopleApi, selectedAccount.address);
|
const kycStatusResult = await getKycStatus(peopleApi, selectedAccount.address);
|
||||||
setKycStatus(status);
|
setKycStatus(kycStatusResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (import.meta.env.DEV) console.error('Error fetching scores and tikis:', error);
|
if (import.meta.env.DEV) console.error('Error fetching scores and tikis:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingScores(false);
|
setLoadingScores(false);
|
||||||
}
|
}
|
||||||
}, [selectedAccount, api, peopleApi, assetHubApi]);
|
}, [selectedAccount, api, peopleApi]);
|
||||||
|
|
||||||
|
const handleStartScoreTracking = async () => {
|
||||||
|
if (!peopleApi || !selectedAccount) {
|
||||||
|
toast({
|
||||||
|
title: "Hata",
|
||||||
|
description: "Lütfen önce cüzdanınızı bağlayın",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStartingScoreTracking(true);
|
||||||
|
try {
|
||||||
|
const injector = await web3FromAddress(selectedAccount.address);
|
||||||
|
const result = await startScoreTracking(peopleApi, selectedAccount.address, injector.signer);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast({
|
||||||
|
title: "Başarılı",
|
||||||
|
description: "Score tracking başlatıldı! Staking score'unuz artık hesaplanacak."
|
||||||
|
});
|
||||||
|
// Refresh scores after starting tracking
|
||||||
|
fetchScoresAndTikis();
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Hata",
|
||||||
|
description: result.error || "Score tracking başlatılamadı",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (import.meta.env.DEV) console.error('Error starting score tracking:', error);
|
||||||
|
toast({
|
||||||
|
title: "Hata",
|
||||||
|
description: error instanceof Error ? error.message : "Score tracking başlatılamadı",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setStartingScoreTracking(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady && assetHubApi && isAssetHubReady) {
|
if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady) {
|
||||||
fetchScoresAndTikis();
|
fetchScoresAndTikis();
|
||||||
}
|
}
|
||||||
}, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, fetchProfile, fetchScoresAndTikis]);
|
}, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, fetchProfile, fetchScoresAndTikis]);
|
||||||
|
|
||||||
const sendVerificationEmail = async () => {
|
const sendVerificationEmail = async () => {
|
||||||
if (!user?.email) {
|
if (!user?.email) {
|
||||||
@@ -435,24 +481,35 @@ export default function Dashboard() {
|
|||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-2xl font-bold text-green-600">
|
||||||
{loadingScores ? '...' : scores.stakingScore}
|
{loadingScores ? '...' : scores.stakingScore}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
{stakingStatus?.isTracking ? (
|
||||||
Validator staking
|
<p className="text-xs text-muted-foreground">
|
||||||
</p>
|
Tracking: {formatDuration(stakingStatus.durationBlocks)}
|
||||||
</CardContent>
|
</p>
|
||||||
</Card>
|
) : selectedAccount ? (
|
||||||
|
<Button
|
||||||
<Card>
|
size="sm"
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
variant="outline"
|
||||||
<CardTitle className="text-sm font-medium">LP Staking Score</CardTitle>
|
className="mt-2 w-full"
|
||||||
<Coins className="h-4 w-4 text-yellow-500" />
|
onClick={handleStartScoreTracking}
|
||||||
</CardHeader>
|
disabled={startingScoreTracking || loadingScores}
|
||||||
<CardContent>
|
>
|
||||||
<div className="text-2xl font-bold text-yellow-600">
|
{startingScoreTracking ? (
|
||||||
{loadingScores ? '...' : scores.lpStakingScore}
|
<>
|
||||||
</div>
|
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
|
||||||
<p className="text-xs text-muted-foreground">
|
Starting...
|
||||||
LP token staking
|
</>
|
||||||
</p>
|
) : (
|
||||||
|
<>
|
||||||
|
<Play className="h-3 w-3 mr-1" />
|
||||||
|
Start Tracking
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Connect wallet to track
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user