From b0c3626e7ae31aafb9aec382642f4c8db374608e Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Fri, 6 Feb 2026 20:01:12 +0300 Subject: [PATCH] feat: add LP staking score and DOT/ETH/BTC tokens --- shared/lib/scores.ts | 77 +++++++++++++++++++++++++-- shared/types/dex.ts | 21 ++++++++ web/src/components/LPStakingModal.tsx | 16 +++++- web/src/pages/Dashboard.tsx | 33 +++++++++--- 4 files changed, 133 insertions(+), 14 deletions(-) diff --git a/shared/lib/scores.ts b/shared/lib/scores.ts index 5b5c84ec..39fc53bc 100644 --- a/shared/lib/scores.ts +++ b/shared/lib/scores.ts @@ -13,6 +13,7 @@ export interface UserScores { trustScore: number; referralScore: number; stakingScore: number; + lpStakingScore: number; tikiScore: 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 { + 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) // ======================================== @@ -306,12 +368,14 @@ export async function getTikiScore( * * @param peopleApi - API for People Chain (trust, referral, tiki, stakingScore pallets) * @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( peopleApi: ApiPromise, address: string, - relayApi?: ApiPromise + relayApi?: ApiPromise, + assetHubApi?: ApiPromise ): Promise { try { if (!peopleApi || !address) { @@ -319,6 +383,7 @@ export async function getAllScores( trustScore: 0, referralScore: 0, stakingScore: 0, + lpStakingScore: 0, tikiScore: 0, totalScore: 0 }; @@ -327,19 +392,22 @@ export async function getAllScores( // Fetch all scores in parallel // - Trust, referral, tiki scores: People Chain // - 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), getReferralScore(peopleApi, address), getStakingScoreFromPallet(peopleApi, address, relayApi), + assetHubApi ? getLpStakingScore(assetHubApi, address) : Promise.resolve(0), getTikiScore(peopleApi, address) ]); - const totalScore = trustScore + referralScore + stakingScore + tikiScore; + const totalScore = trustScore + referralScore + stakingScore + lpStakingScore + tikiScore; return { trustScore, referralScore, stakingScore, + lpStakingScore, tikiScore, totalScore }; @@ -349,6 +417,7 @@ export async function getAllScores( trustScore: 0, referralScore: 0, stakingScore: 0, + lpStakingScore: 0, tikiScore: 0, totalScore: 0 }; diff --git a/shared/types/dex.ts b/shared/types/dex.ts index f421554e..c6e1493c 100644 --- a/shared/types/dex.ts +++ b/shared/types/dex.ts @@ -110,6 +110,27 @@ export const KNOWN_TOKENS: Record = { decimals: 6, 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) diff --git a/web/src/components/LPStakingModal.tsx b/web/src/components/LPStakingModal.tsx index b52bd4c5..165b0526 100644 --- a/web/src/components/LPStakingModal.tsx +++ b/web/src/components/LPStakingModal.tsx @@ -62,17 +62,29 @@ export const LPStakingModal: React.FC = ({ isOpen, onClose const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? poolId; let userStaked = '0'; - const pendingRewards = '0'; + let pendingRewards = '0'; let lpBalance = '0'; if (selectedAccount) { try { const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, selectedAccount.address]); 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'; } + // Fetch pending rewards from the pallet + try { + const rewardsResult = await (assetHubApi.call as { assetRewardsApi?: { pendingRewards: (poolId: number, account: string) => Promise } }) + .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); if (lpBal && (lpBal as { isSome: boolean }).isSome) { const lpData = (lpBal as { unwrap: () => { toJSON: () => { balance: string } } }).unwrap().toJSON(); diff --git a/web/src/pages/Dashboard.tsx b/web/src/pages/Dashboard.tsx index 6e483659..1562c74c 100644 --- a/web/src/pages/Dashboard.tsx +++ b/web/src/pages/Dashboard.tsx @@ -7,7 +7,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useAuth } from '@/contexts/AuthContext'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; 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 { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki'; import { getAllScores, type UserScores } from '@pezkuwi/lib/scores'; @@ -18,7 +18,7 @@ import { ReferralDashboard } from '@/components/referral/ReferralDashboard'; export default function Dashboard() { const { user } = useAuth(); - const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount } = usePezkuwi(); + const { api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, selectedAccount } = usePezkuwi(); const navigate = useNavigate(); const { toast } = useToast(); const [profile, setProfile] = useState | null>(null); @@ -28,6 +28,7 @@ export default function Dashboard() { trustScore: 0, referralScore: 0, stakingScore: 0, + lpStakingScore: 0, tikiScore: 0, totalScore: 0 }); @@ -105,10 +106,11 @@ export default function Dashboard() { setLoadingScores(true); 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 // - 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); // Fetch tikis from People Chain (tiki pallet is on People Chain) @@ -127,14 +129,14 @@ export default function Dashboard() { } finally { setLoadingScores(false); } - }, [selectedAccount, api, peopleApi]); + }, [selectedAccount, api, peopleApi, assetHubApi]); useEffect(() => { fetchProfile(); - if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady) { + if (selectedAccount && api && isApiReady && peopleApi && isPeopleReady && assetHubApi && isAssetHubReady) { fetchScoresAndTikis(); } - }, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, fetchProfile, fetchScoresAndTikis]); + }, [user, selectedAccount, api, isApiReady, peopleApi, isPeopleReady, assetHubApi, isAssetHubReady, fetchProfile, fetchScoresAndTikis]); const sendVerificationEmail = async () => { if (!user?.email) { @@ -434,7 +436,22 @@ export default function Dashboard() { {loadingScores ? '...' : scores.stakingScore}

- From pallet_staking_score + Validator staking +

+ + + + + + LP Staking Score + + + +
+ {loadingScores ? '...' : scores.lpStakingScore} +
+

+ LP token staking