From 67b30daca895248a5fa45de12274c79300c7b717 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sat, 7 Feb 2026 02:20:04 +0300 Subject: [PATCH] fix: tiki score, staking lookup, LP balance, teleport, DOT swap - Fix tiki: use userTikis storage instead of userRoles - Add tiki name to score mapping (welati=10, serok=50, etc) - Improve staking ledger lookup with debug logging - Fix LP balance fetching using poolId directly - Change teleport placeholder from 0.5 to empty - Add DOT token to swap list with 10 decimals --- package.json | 2 +- src/components/wallet/FundFeesModal.tsx | 6 +- src/components/wallet/LPStakingModal.tsx | 22 ++-- src/components/wallet/SwapModal.tsx | 20 +++- src/lib/scores.ts | 123 +++++++++++++++++++---- src/sections/Rewards.tsx | 4 + src/version.json | 6 +- 7 files changed, 145 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index f605feb..07c0b48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.126", + "version": "1.0.127", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/components/wallet/FundFeesModal.tsx b/src/components/wallet/FundFeesModal.tsx index b8bdc68..a579235 100644 --- a/src/components/wallet/FundFeesModal.tsx +++ b/src/components/wallet/FundFeesModal.tsx @@ -45,7 +45,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) { const { hapticImpact, showAlert } = useTelegram(); const [targetChain, setTargetChain] = useState('asset-hub'); - const [amount, setAmount] = useState('0.5'); + const [amount, setAmount] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [txStatus, setTxStatus] = useState<'idle' | 'signing' | 'pending' | 'success' | 'error'>( 'idle' @@ -244,7 +244,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) { // Reset after success setTimeout(() => { - setAmount('0.5'); + setAmount(''); setTxStatus('idle'); onClose(); }, 2000); @@ -405,7 +405,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) { step="0.0001" value={amount} onChange={(e) => setAmount(e.target.value)} - placeholder="0.5" + placeholder="Mîqdar" className="w-full px-4 py-3 bg-muted rounded-xl text-lg font-mono" disabled={isTransferring} /> diff --git a/src/components/wallet/LPStakingModal.tsx b/src/components/wallet/LPStakingModal.tsx index c16ee40..74ca451 100644 --- a/src/components/wallet/LPStakingModal.tsx +++ b/src/components/wallet/LPStakingModal.tsx @@ -67,7 +67,8 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const poolData = value.toJSON() as any; - const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? poolId; + // LP token ID in poolAssets pallet matches the pool ID (0, 1, 2) + const lpTokenId = poolId; let userStaked = '0'; let pendingRewards = '0'; @@ -84,16 +85,23 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) { const stakeData = stakeInfo.unwrap().toJSON(); userStaked = stakeData.amount || '0'; } + } catch (err) { + console.error('Error fetching stake info:', err); + } - // Fetch LP balance + // Fetch LP balance from poolAssets pallet + try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const lpBal = await (assetHubApi.query.poolAssets as any).account(lpTokenId, address); - if (lpBal && lpBal.isSome) { - const lpData = lpBal.unwrap().toJSON(); - lpBalance = lpData.balance || '0'; + if (lpBal) { + // Handle both Option and direct AccountData + const lpData = lpBal.isSome ? lpBal.unwrap().toJSON() : lpBal.toJSON(); + if (lpData && lpData.balance) { + lpBalance = lpData.balance.toString(); + } } - } catch { - // Ignore errors + } catch (err) { + console.error('Error fetching LP balance for pool', poolId, ':', err); } } diff --git a/src/components/wallet/SwapModal.tsx b/src/components/wallet/SwapModal.tsx index ded2853..6a12f67 100644 --- a/src/components/wallet/SwapModal.tsx +++ b/src/components/wallet/SwapModal.tsx @@ -19,6 +19,7 @@ const TOKENS = [ { symbol: 'HEZ', name: 'Hezkurd', assetId: -1, decimals: 12, icon: '/tokens/HEZ.png' }, { symbol: 'PEZ', name: 'Pezkuwi', assetId: 1, decimals: 12, icon: '/tokens/PEZ.png' }, { symbol: 'USDT', name: 'Tether', assetId: 1000, decimals: 6, icon: '/tokens/USDT.png' }, + { symbol: 'DOT', name: 'Polkadot', assetId: 1001, decimals: 10, icon: '/tokens/DOT.png' }, ]; // Native token ID for relay chain HEZ @@ -51,6 +52,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) { HEZ: '0', PEZ: '0', USDT: '0', + DOT: '0', }); // Fetch balances from Asset Hub (where swaps happen) @@ -84,11 +86,19 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) { ? (parseInt(usdtResult.unwrap().balance.toString()) / 1e6).toFixed(2) : '0.00'; + // DOT balance (Asset 1001, 10 decimals) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dotResult = await (assetHubApi.query.assets as any).account(1001, keypair.address); + const dotBalance = dotResult.isSome + ? (parseInt(dotResult.unwrap().balance.toString()) / 1e10).toFixed(4) + : '0.0000'; + // Update all balances at once setBalances({ HEZ: hezBalance, PEZ: pezBalance, USDT: usdtBalance, + DOT: dotBalance, }); } catch (err) { console.error('Failed to fetch balances:', err); @@ -127,8 +137,14 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) { if (poolInfo && !poolInfo.isEmpty) { // Get quote from runtime API - const decimals1 = asset1 === 1000 ? 6 : 12; - const decimals2 = asset2 === 1000 ? 6 : 12; + // USDT has 6 decimals, DOT has 10 decimals, others have 12 + const getDecimals = (id: number) => { + if (id === 1000) return 6; // USDT + if (id === 1001) return 10; // DOT + return 12; // HEZ, PEZ + }; + const decimals1 = getDecimals(asset1); + const decimals2 = getDecimals(asset2); const oneUnit = BigInt(Math.pow(10, decimals1)); const quote = await ( diff --git a/src/lib/scores.ts b/src/lib/scores.ts index 9f04208..356cf0f 100644 --- a/src/lib/scores.ts +++ b/src/lib/scores.ts @@ -64,6 +64,7 @@ function saveStakingTrackingData(data: StakingTrackingData): void { /** * Fetch staking details directly from Relay Chain + * In newer Substrate versions, ledger is keyed by stash address */ export async function fetchRelayStakingDetails( relayApi: ApiPromise, @@ -71,36 +72,64 @@ export async function fetchRelayStakingDetails( ): Promise<{ stakedAmount: bigint; nominationsCount: number } | null> { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (!(relayApi?.query as any)?.staking) return null; + if (!(relayApi?.query as any)?.staking) { + console.log('[Staking] staking pallet not found'); + return null; + } + let stashAddress = address; + let active = 0n; + + // In newer Substrate, ledger is keyed by stash address directly // eslint-disable-next-line @typescript-eslint/no-explicit-any let ledger = await (relayApi.query.staking as any).ledger?.(address); - let stashAddress = address; - // If no ledger, check if this is a stash account - if (!ledger || ledger.isEmpty || ledger.isNone) { + // Check if ledger exists and has data + if (ledger && !ledger.isEmpty && !ledger.isNone) { + // Ledger might be wrapped in Option + const unwrapped = ledger.isSome ? ledger.unwrap() : ledger; + const ledgerJson = unwrapped.toJSON() as { active?: string | number; stash?: string }; + console.log('[Staking] Ledger found for', address, ':', ledgerJson); + active = BigInt(ledgerJson?.active || 0); + if (ledgerJson?.stash) { + stashAddress = ledgerJson.stash; + } + } else { + // Fallback: check if this is a stash account with a controller // eslint-disable-next-line @typescript-eslint/no-explicit-any const bonded = await (relayApi.query.staking as any).bonded?.(address); if (bonded && !bonded.isEmpty && !bonded.isNone) { const controller = bonded.toString(); + console.log('[Staking] Address', address, 'is stash, controller:', controller); // eslint-disable-next-line @typescript-eslint/no-explicit-any ledger = await (relayApi.query.staking as any).ledger?.(controller); - stashAddress = address; + if (ledger && !ledger.isEmpty && !ledger.isNone) { + const unwrapped = ledger.isSome ? ledger.unwrap() : ledger; + const ledgerJson = unwrapped.toJSON() as { active?: string | number }; + console.log('[Staking] Ledger from controller:', ledgerJson); + active = BigInt(ledgerJson?.active || 0); + } + } else { + console.log('[Staking] No ledger or bonded found for', address); } } - if (!ledger || ledger.isEmpty || ledger.isNone) { + if (active === 0n) { return null; } - const ledgerJson = ledger.toJSON() as { active?: string | number }; - const active = BigInt(ledgerJson?.active || 0); - + // Get nominations // eslint-disable-next-line @typescript-eslint/no-explicit-any const nominations = await (relayApi.query.staking as any).nominators?.(stashAddress); const nominationsJson = nominations?.toJSON() as { targets?: unknown[] } | null; const nominationsCount = nominationsJson?.targets?.length || 0; + console.log( + '[Staking] Final result - active:', + active.toString(), + 'nominations:', + nominationsCount + ); return { stakedAmount: active, nominationsCount, @@ -224,25 +253,68 @@ const TIKI_ROLE_SCORES: Record = { }; /** - * Fetch user's tiki roles from People Chain + * Tiki role name to score mapping + * Welati (citizen) is the basic role with score 10 + */ +const TIKI_NAME_SCORES: Record = { + welati: 10, + parlementer: 30, + serokimeclise: 40, + serok: 50, + wezir: 40, + endamediwane: 30, + dadger: 35, + dozger: 35, + mamoste: 25, + perwerdekar: 25, + bazargan: 20, +}; + +/** + * Fetch user's tikis from People Chain + * Storage: tiki.userTikis(address) -> Vec */ export async function fetchUserTikis(peopleApi: ApiPromise, address: string): Promise { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (!(peopleApi?.query as any)?.tiki) return []; + if (!(peopleApi?.query as any)?.tiki) { + console.log('[Tiki] tiki pallet not found'); + return []; + } + // Try userTikis first (actual storage name) // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result = await (peopleApi.query.tiki as any).userRoles?.(address); + let result = await (peopleApi.query.tiki as any).userTikis?.(address); - if (!result || result.isEmpty) return []; + // Fallback to userRoles if userTikis doesn't exist + if (!result && (peopleApi.query.tiki as any).userRoles) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result = await (peopleApi.query.tiki as any).userRoles?.(address); + } + if (!result || result.isEmpty) { + console.log('[Tiki] No tikis found for', address); + return []; + } + + // Result is Vec which are enum variants as strings // eslint-disable-next-line @typescript-eslint/no-explicit-any - const roles = result.toJSON() as any[]; - return roles.map((role) => ({ - roleId: role.roleId || role.role_id || 0, - level: role.level || 0, - name: role.name || 'Unknown', - })); + const tikis = result.toJSON() as any[]; + console.log('[Tiki] Raw tikis for', address, ':', tikis); + + return tikis.map((tiki, index) => { + // Tiki can be a string (enum variant name) or object + const name = typeof tiki === 'string' ? tiki : tiki.name || tiki.role || 'Unknown'; + const nameLower = name.toLowerCase(); + const score = TIKI_NAME_SCORES[nameLower] || 10; // Default to 10 if unknown + + return { + roleId: index + 1, + level: 1, + name: name, + score: score, + }; + }); } catch (err) { console.error('Failed to fetch tiki roles:', err); return []; @@ -250,7 +322,8 @@ export async function fetchUserTikis(peopleApi: ApiPromise, address: string): Pr } /** - * Calculate tiki score from user's roles + * Calculate tiki score from user's tikis + * Uses the score property set during fetch, or looks up by name */ export function calculateTikiScore(tikis: TikiInfo[]): number { if (!tikis.length) return 0; @@ -258,10 +331,16 @@ export function calculateTikiScore(tikis: TikiInfo[]): number { // Get highest role score let maxScore = 0; for (const tiki of tikis) { - const roleScore = TIKI_ROLE_SCORES[tiki.roleId] || 0; - maxScore = Math.max(maxScore, roleScore); + // Use score from tiki if available, otherwise lookup by roleId or name + const tikiScore = + (tiki as TikiInfo & { score?: number }).score || + TIKI_ROLE_SCORES[tiki.roleId] || + TIKI_NAME_SCORES[tiki.name.toLowerCase()] || + 10; // Default welati score + maxScore = Math.max(maxScore, tikiScore); } + console.log('[Tiki] Calculated score:', maxScore, 'from tikis:', tikis); return Math.min(maxScore, 50); // Capped at 50 } diff --git a/src/sections/Rewards.tsx b/src/sections/Rewards.tsx index ccd8e75..47708c9 100644 --- a/src/sections/Rewards.tsx +++ b/src/sections/Rewards.tsx @@ -101,12 +101,16 @@ export function RewardsSection() { return; } + console.log('[Scores] Fetching scores for', address); + console.log('[Scores] API connected:', !!api, 'People API:', !!peopleApi); + setScoresLoading(true); try { const [scores, staking] = await Promise.all([ getAllScoresWithFallback(peopleApi, api, address), api ? getFrontendStakingScore(api, address) : Promise.resolve(null), ]); + console.log('[Scores] Results:', { scores, staking }); setUserScores(scores); setStakingDetails(staking); } catch (err) { diff --git a/src/version.json b/src/version.json index a399ae6..f91a201 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.126", - "buildTime": "2026-02-06T23:07:37.278Z", - "buildNumber": 1770419257279 + "version": "1.0.127", + "buildTime": "2026-02-06T23:20:04.537Z", + "buildNumber": 1770420004537 }