mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 05:37:56 +00:00
feat: add LP staking score and DOT/ETH/BTC tokens
This commit is contained in:
+73
-4
@@ -13,6 +13,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -274,6 +275,67 @@ export async function getStakingScoreFromPallet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// LP STAKING SCORE (Asset Hub assetRewards pallet)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LP staking score from Asset Hub
|
||||||
|
* Based on total LP tokens staked across all pools
|
||||||
|
*
|
||||||
|
* @param assetHubApi - API for Asset Hub (assetRewards pallet)
|
||||||
|
* @param address - User's blockchain address
|
||||||
|
*/
|
||||||
|
export async function getLpStakingScore(
|
||||||
|
assetHubApi: ApiPromise,
|
||||||
|
address: string
|
||||||
|
): Promise<number> {
|
||||||
|
try {
|
||||||
|
if (!assetHubApi?.query?.assetRewards?.poolStakers) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query all staking pool entries
|
||||||
|
const poolEntries = await assetHubApi.query.assetRewards.pools.entries();
|
||||||
|
|
||||||
|
let totalStaked = BigInt(0);
|
||||||
|
|
||||||
|
for (const [key] of poolEntries) {
|
||||||
|
const poolId = parseInt(key.args[0].toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, address]);
|
||||||
|
if (stakeInfo && (stakeInfo as { isSome: boolean }).isSome) {
|
||||||
|
const stakeData = (stakeInfo as { unwrap: () => { toJSON: () => { amount: string } } }).unwrap().toJSON();
|
||||||
|
totalStaked += BigInt(stakeData.amount || '0');
|
||||||
|
}
|
||||||
|
} 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) {
|
||||||
|
console.error('Error fetching LP staking score:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// TIKI SCORE (from lib/tiki.ts)
|
// TIKI SCORE (from lib/tiki.ts)
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -306,12 +368,14 @@ export async function getTikiScore(
|
|||||||
*
|
*
|
||||||
* @param peopleApi - API for People Chain (trust, referral, tiki, stakingScore pallets)
|
* @param peopleApi - API for People Chain (trust, referral, tiki, stakingScore pallets)
|
||||||
* @param address - User's blockchain address
|
* @param address - User's blockchain address
|
||||||
* @param relayApi - Optional API for Relay Chain (staking pallet for staking score calculation)
|
* @param relayApi - Optional API for Relay Chain (staking pallet for validator staking score)
|
||||||
|
* @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) {
|
||||||
@@ -319,6 +383,7 @@ 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
|
||||||
};
|
};
|
||||||
@@ -327,19 +392,22 @@ export async function getAllScores(
|
|||||||
// Fetch all scores in parallel
|
// Fetch all scores in parallel
|
||||||
// - Trust, referral, tiki scores: People Chain
|
// - Trust, referral, tiki scores: People Chain
|
||||||
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
||||||
const [trustScore, referralScore, stakingScore, tikiScore] = await Promise.all([
|
// - 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),
|
getStakingScoreFromPallet(peopleApi, address, relayApi),
|
||||||
|
assetHubApi ? getLpStakingScore(assetHubApi, address) : Promise.resolve(0),
|
||||||
getTikiScore(peopleApi, address)
|
getTikiScore(peopleApi, address)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const totalScore = trustScore + referralScore + stakingScore + tikiScore;
|
const totalScore = trustScore + referralScore + stakingScore + lpStakingScore + tikiScore;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trustScore,
|
trustScore,
|
||||||
referralScore,
|
referralScore,
|
||||||
stakingScore,
|
stakingScore,
|
||||||
|
lpStakingScore,
|
||||||
tikiScore,
|
tikiScore,
|
||||||
totalScore
|
totalScore
|
||||||
};
|
};
|
||||||
@@ -349,6 +417,7 @@ 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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -110,6 +110,27 @@ export const KNOWN_TOKENS: Record<number, TokenInfo> = {
|
|||||||
decimals: 6,
|
decimals: 6,
|
||||||
logo: '/shared/images/USDT(hez)logo.png',
|
logo: '/shared/images/USDT(hez)logo.png',
|
||||||
},
|
},
|
||||||
|
1001: {
|
||||||
|
id: 1001,
|
||||||
|
symbol: 'DOT',
|
||||||
|
name: 'Wrapped DOT',
|
||||||
|
decimals: 10,
|
||||||
|
logo: '/shared/images/dot.png',
|
||||||
|
},
|
||||||
|
1002: {
|
||||||
|
id: 1002,
|
||||||
|
symbol: 'ETH',
|
||||||
|
name: 'Wrapped ETH',
|
||||||
|
decimals: 18,
|
||||||
|
logo: '/shared/images/etherium.png',
|
||||||
|
},
|
||||||
|
1003: {
|
||||||
|
id: 1003,
|
||||||
|
symbol: 'BTC',
|
||||||
|
name: 'Wrapped BTC',
|
||||||
|
decimals: 8,
|
||||||
|
logo: '/shared/images/bitcoin.png',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// LP Token info (poolAssets pallet - separate from assets pallet)
|
// LP Token info (poolAssets pallet - separate from assets pallet)
|
||||||
|
|||||||
@@ -62,17 +62,29 @@ export const LPStakingModal: React.FC<LPStakingModalProps> = ({ isOpen, onClose
|
|||||||
const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? poolId;
|
const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? poolId;
|
||||||
|
|
||||||
let userStaked = '0';
|
let userStaked = '0';
|
||||||
const pendingRewards = '0';
|
let pendingRewards = '0';
|
||||||
let lpBalance = '0';
|
let lpBalance = '0';
|
||||||
|
|
||||||
if (selectedAccount) {
|
if (selectedAccount) {
|
||||||
try {
|
try {
|
||||||
const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, selectedAccount.address]);
|
const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, selectedAccount.address]);
|
||||||
if (stakeInfo && (stakeInfo as { isSome: boolean }).isSome) {
|
if (stakeInfo && (stakeInfo as { isSome: boolean }).isSome) {
|
||||||
const stakeData = (stakeInfo as { unwrap: () => { toJSON: () => { amount: string } } }).unwrap().toJSON();
|
const stakeData = (stakeInfo as { unwrap: () => { toJSON: () => { amount: string; rewardPerTokenPaid?: string } } }).unwrap().toJSON();
|
||||||
userStaked = stakeData.amount || '0';
|
userStaked = stakeData.amount || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch pending rewards from the pallet
|
||||||
|
try {
|
||||||
|
const rewardsResult = await (assetHubApi.call as { assetRewardsApi?: { pendingRewards: (poolId: number, account: string) => Promise<unknown> } })
|
||||||
|
.assetRewardsApi?.pendingRewards(poolId, selectedAccount.address);
|
||||||
|
if (rewardsResult && typeof rewardsResult === 'object' && 'toString' in rewardsResult) {
|
||||||
|
pendingRewards = rewardsResult.toString();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If runtime API not available, try direct calculation
|
||||||
|
// pendingRewards stays 0
|
||||||
|
}
|
||||||
|
|
||||||
const lpBal = await assetHubApi.query.poolAssets.account(lpTokenId, selectedAccount.address);
|
const lpBal = await assetHubApi.query.poolAssets.account(lpTokenId, selectedAccount.address);
|
||||||
if (lpBal && (lpBal as { isSome: boolean }).isSome) {
|
if (lpBal && (lpBal as { isSome: boolean }).isSome) {
|
||||||
const lpData = (lpBal as { unwrap: () => { toJSON: () => { balance: string } } }).unwrap().toJSON();
|
const lpData = (lpBal as { unwrap: () => { toJSON: () => { balance: string } } }).unwrap().toJSON();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 } from 'lucide-react';
|
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus, Coins } 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, type UserScores } from '@pezkuwi/lib/scores';
|
||||||
@@ -18,7 +18,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, selectedAccount } = usePezkuwi();
|
const { api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, 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,6 +28,7 @@ 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
|
||||||
});
|
});
|
||||||
@@ -105,10 +106,11 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
setLoadingScores(true);
|
setLoadingScores(true);
|
||||||
try {
|
try {
|
||||||
// Fetch all scores from blockchain (includes trust, referral, staking, tiki)
|
// Fetch all scores from blockchain (includes trust, referral, staking, lpStaking, tiki)
|
||||||
// - Trust, referral, tiki: People Chain
|
// - Trust, referral, tiki: People Chain
|
||||||
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
// - Staking score: People Chain (stakingScore pallet) + Relay Chain (staking.ledger)
|
||||||
const allScores = await getAllScores(peopleApi, selectedAccount.address, api);
|
// - LP Staking score: Asset Hub (assetRewards pallet)
|
||||||
|
const allScores = await getAllScores(peopleApi, selectedAccount.address, api, assetHubApi || undefined);
|
||||||
setScores(allScores);
|
setScores(allScores);
|
||||||
|
|
||||||
// Fetch tikis from People Chain (tiki pallet is on People Chain)
|
// Fetch tikis from People Chain (tiki pallet is on People Chain)
|
||||||
@@ -127,14 +129,14 @@ export default function Dashboard() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoadingScores(false);
|
setLoadingScores(false);
|
||||||
}
|
}
|
||||||
}, [selectedAccount, api, peopleApi]);
|
}, [selectedAccount, api, peopleApi, assetHubApi]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady) {
|
if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady && assetHubApi && isAssetHubReady) {
|
||||||
fetchScoresAndTikis();
|
fetchScoresAndTikis();
|
||||||
}
|
}
|
||||||
}, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, fetchProfile, fetchScoresAndTikis]);
|
}, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, fetchProfile, fetchScoresAndTikis]);
|
||||||
|
|
||||||
const sendVerificationEmail = async () => {
|
const sendVerificationEmail = async () => {
|
||||||
if (!user?.email) {
|
if (!user?.email) {
|
||||||
@@ -434,7 +436,22 @@ export default function Dashboard() {
|
|||||||
{loadingScores ? '...' : scores.stakingScore}
|
{loadingScores ? '...' : scores.stakingScore}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
From pallet_staking_score
|
Validator staking
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">LP Staking Score</CardTitle>
|
||||||
|
<Coins className="h-4 w-4 text-yellow-500" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold text-yellow-600">
|
||||||
|
{loadingScores ? '...' : scores.lpStakingScore}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
LP token staking
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user