diff --git a/package.json b/package.json index 11c5e6b..1ca76dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.211", + "version": "1.0.213", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/i18n/translations/ar.ts b/src/i18n/translations/ar.ts index 15cf287..cc4bd13 100644 --- a/src/i18n/translations/ar.ts +++ b/src/i18n/translations/ar.ts @@ -134,6 +134,16 @@ const ar: Translations = { signingBlockchain: 'جاري التوقيع على البلوكتشين', citizenshipConfirmed: 'مبروك! أنت الآن مواطن!', citizenshipFailed: 'فشل التأكيد', + startTracking: 'بدء التتبع', + startTrackingDesc: 'تفعيل تتبع نقاط التخزين', + startingTracking: 'جاري بدء التتبع...', + trackingStarted: 'بدأ التتبع!', + trackingFailed: 'فشل بدء التتبع', + recordTrustScore: 'تسجيل نقاط الثقة', + recordTrustDesc: 'سجل نقاطك لمكافآت هذه الفترة', + recordingTrustScore: 'جاري تسجيل نقاط الثقة...', + trustScoreRecorded: 'تم تسجيل نقاط الثقة!', + trustScoreRecordFailed: 'فشل تسجيل النقاط', }, wallet: { diff --git a/src/i18n/translations/ckb.ts b/src/i18n/translations/ckb.ts index c08cb08..78256fa 100644 --- a/src/i18n/translations/ckb.ts +++ b/src/i18n/translations/ckb.ts @@ -135,6 +135,16 @@ const ckb: Translations = { signingBlockchain: 'لەسەر بلۆکچەین واژوو دەکرێت', citizenshipConfirmed: 'پیرۆزبێت! تۆ ئێستا هاوڵاتیت!', citizenshipFailed: 'پشتڕاستکردنەوە سەرنەکەوت', + startTracking: 'شوێنکەوتن دەست پێ بکە', + startTrackingDesc: 'شوێنکەوتنی خاڵی ستەیکینگ چالاک بکە', + startingTracking: 'شوێنکەوتن دەست پێ دەکات...', + trackingStarted: 'شوێنکەوتن دەست پێ کرد!', + trackingFailed: 'دەست پێکردنی شوێنکەوتن سەرنەکەوت', + recordTrustScore: 'خاڵی متمانە تۆمار بکە', + recordTrustDesc: 'خاڵەکەت بۆ خەڵاتەکانی ئەم سەردەمە تۆمار بکە', + recordingTrustScore: 'خاڵ تۆمار دەکرێت...', + trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!', + trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت', }, wallet: { diff --git a/src/i18n/translations/en.ts b/src/i18n/translations/en.ts index 792aaca..9e88a82 100644 --- a/src/i18n/translations/en.ts +++ b/src/i18n/translations/en.ts @@ -134,6 +134,16 @@ const en: Translations = { signingBlockchain: 'Signing on blockchain', citizenshipConfirmed: 'Congratulations! You are now a citizen!', citizenshipFailed: 'Confirmation failed', + startTracking: 'Start Tracking', + startTrackingDesc: 'Enable staking score tracking', + startingTracking: 'Starting tracking...', + trackingStarted: 'Tracking started!', + trackingFailed: 'Failed to start tracking', + recordTrustScore: 'Record Trust Score', + recordTrustDesc: "Record your score for this epoch's rewards", + recordingTrustScore: 'Recording trust score...', + trustScoreRecorded: 'Trust score recorded!', + trustScoreRecordFailed: 'Failed to record score', }, wallet: { diff --git a/src/i18n/translations/fa.ts b/src/i18n/translations/fa.ts index 9fdadb9..9add196 100644 --- a/src/i18n/translations/fa.ts +++ b/src/i18n/translations/fa.ts @@ -134,6 +134,16 @@ const fa: Translations = { signingBlockchain: 'در حال امضا روی بلاکچین', citizenshipConfirmed: 'تبریک! شما اکنون شهروند هستید!', citizenshipFailed: 'تایید ناموفق بود', + startTracking: 'شروع ردیابی', + startTrackingDesc: 'ردیابی امتیاز سهام‌گذاری را فعال کنید', + startingTracking: 'در حال شروع ردیابی...', + trackingStarted: 'ردیابی شروع شد!', + trackingFailed: 'شروع ردیابی ناموفق بود', + recordTrustScore: 'ثبت امتیاز اعتماد', + recordTrustDesc: 'امتیاز خود را برای پاداش‌های این دوره ثبت کنید', + recordingTrustScore: 'در حال ثبت امتیاز اعتماد...', + trustScoreRecorded: 'امتیاز اعتماد ثبت شد!', + trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود', }, wallet: { diff --git a/src/i18n/translations/krd.ts b/src/i18n/translations/krd.ts index 0635228..169e385 100644 --- a/src/i18n/translations/krd.ts +++ b/src/i18n/translations/krd.ts @@ -137,8 +137,18 @@ const krd: Translations = { confirmCitizenship: 'Welat\u00eeb\u00fbn\u00ea Pi\u015ftrast Bike', confirmingCitizenship: 'Welat\u00eeb\u00fbn t\u00ea pi\u015ftrastkirin...', signingBlockchain: 'Li blockchain t\u00ea \u00eemzekirin', - citizenshipConfirmed: 'P\u00eeroz be! Tu b\u00fby\u00ee welat\u00ee!', - citizenshipFailed: 'Pi\u015ftrastkirin biserneket', + citizenshipConfirmed: 'Pîroz be! Tu bûyî welatî!', + citizenshipFailed: 'Piştrastkirin biserneket', + startTracking: 'Şopandinê Dest Pê Bike', + startTrackingDesc: 'Şopandina pûana stakingê çalak bike', + startingTracking: 'Şopandin tê destpêkirin...', + trackingStarted: 'Şopandin dest pê kir!', + trackingFailed: 'Destpêkirina şopandinê biserneket', + recordTrustScore: 'Pûana Pêbaweriyê Tomar Bike', + recordTrustDesc: 'Pûana xwe ji bo xelatên vê serdemê tomar bike', + recordingTrustScore: 'Pûan tê tomarkirin...', + trustScoreRecorded: 'Pûana pêbaweriyê hat tomarkirin!', + trustScoreRecordFailed: 'Tomarkirina pûanê biserneket', }, wallet: { diff --git a/src/i18n/translations/tr.ts b/src/i18n/translations/tr.ts index ad58e79..680a652 100644 --- a/src/i18n/translations/tr.ts +++ b/src/i18n/translations/tr.ts @@ -132,8 +132,18 @@ const tr: Translations = { confirmCitizenship: 'Vatanda\u015fl\u0131\u011f\u0131 Onayla', confirmingCitizenship: 'Vatanda\u015fl\u0131k onaylan\u0131yor...', signingBlockchain: "Blockchain'de imzalan\u0131yor", - citizenshipConfirmed: 'Tebrikler! Art\u0131k vatanda\u015fs\u0131n\u0131z!', - citizenshipFailed: 'Onay ba\u015far\u0131s\u0131z oldu', + citizenshipConfirmed: 'Tebrikler! Artık vatandaşsınız!', + citizenshipFailed: 'Onay başarısız oldu', + startTracking: 'Takibi Başlat', + startTrackingDesc: 'Staking puan takibini etkinleştirin', + startingTracking: 'Takip başlatılıyor...', + trackingStarted: 'Takip başlatıldı!', + trackingFailed: 'Takip başlatılamadı', + recordTrustScore: 'Güven Puanını Kaydet', + recordTrustDesc: 'Bu dönemin ödülleri için puanınızı kaydedin', + recordingTrustScore: 'Güven puanı kaydediliyor...', + trustScoreRecorded: 'Güven puanı kaydedildi!', + trustScoreRecordFailed: 'Puan kaydedilemedi', }, wallet: { diff --git a/src/i18n/types.ts b/src/i18n/types.ts index adb650f..abd6ef7 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -136,6 +136,16 @@ export interface Translations { signingBlockchain: string; citizenshipConfirmed: string; citizenshipFailed: string; + startTracking: string; + startTrackingDesc: string; + startingTracking: string; + trackingStarted: string; + trackingFailed: string; + recordTrustScore: string; + recordTrustDesc: string; + recordingTrustScore: string; + trustScoreRecorded: string; + trustScoreRecordFailed: string; }; // Wallet section diff --git a/src/lib/scores.ts b/src/lib/scores.ts index e872b0e..f409120 100644 --- a/src/lib/scores.ts +++ b/src/lib/scores.ts @@ -11,6 +11,7 @@ */ import type { ApiPromise } from '@pezkuwi/api'; +import type { KeyringPair } from '@pezkuwi/keyring/types'; // ======================================== // TYPE DEFINITIONS @@ -425,6 +426,130 @@ export async function getPerwerdeScore( } } +// ======================================== +// SCORE TRACKING & RECORDING (People Chain Extrinsics) +// ======================================== + +/** + * Start staking score tracking on People Chain. + * Calls stakingScore.startScoreTracking() - no arguments. + * This enables the noter to cache staking details. + */ +export async function startScoreTracking( + peopleApi: ApiPromise, + keypair: KeyringPair +): Promise<{ success: boolean; error?: string }> { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tx = peopleApi.tx as any; + if (!tx?.stakingScore?.startScoreTracking) { + return { success: false, error: 'stakingScore pallet not available' }; + } + + const result = await new Promise<{ success: boolean; error?: string }>((resolve) => { + tx.stakingScore + .startScoreTracking() + .signAndSend( + keypair, + { nonce: -1 }, + ({ + status, + dispatchError, + }: { + status: { + isInBlock: boolean; + isFinalized: boolean; + }; + dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string }; + }) => { + if (status.isInBlock || status.isFinalized) { + if (dispatchError) { + let errorMessage = 'startScoreTracking failed'; + if (dispatchError.isModule) { + const decoded = peopleApi.registry.findMetaError( + dispatchError.asModule as Parameters[0] + ); + errorMessage = `${decoded.section}.${decoded.name}`; + } + resolve({ success: false, error: errorMessage }); + return; + } + resolve({ success: true }); + } + } + ) + .catch((error: Error) => resolve({ success: false, error: error.message })); + }); + + return result; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} + +/** + * Record trust score for current epoch on People Chain. + * Calls pezRewards.recordTrustScore() - no arguments. + * Required for PEZ epoch rewards. + */ +export async function recordTrustScore( + peopleApi: ApiPromise, + keypair: KeyringPair +): Promise<{ success: boolean; error?: string }> { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tx = peopleApi.tx as any; + if (!tx?.pezRewards?.recordTrustScore) { + return { success: false, error: 'pezRewards pallet not available' }; + } + + const result = await new Promise<{ success: boolean; error?: string }>((resolve) => { + tx.pezRewards + .recordTrustScore() + .signAndSend( + keypair, + { nonce: -1 }, + ({ + status, + dispatchError, + }: { + status: { + isInBlock: boolean; + isFinalized: boolean; + }; + dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string }; + }) => { + if (status.isInBlock || status.isFinalized) { + if (dispatchError) { + let errorMessage = 'recordTrustScore failed'; + if (dispatchError.isModule) { + const decoded = peopleApi.registry.findMetaError( + dispatchError.asModule as Parameters[0] + ); + errorMessage = `${decoded.section}.${decoded.name}`; + } + resolve({ success: false, error: errorMessage }); + return; + } + resolve({ success: true }); + } + } + ) + .catch((error: Error) => resolve({ success: false, error: error.message })); + }); + + return result; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} + // ======================================== // COMPREHENSIVE SCORE FETCHING // ======================================== diff --git a/src/sections/Rewards.tsx b/src/sections/Rewards.tsx index a55f716..c216cbb 100644 --- a/src/sections/Rewards.tsx +++ b/src/sections/Rewards.tsx @@ -21,6 +21,8 @@ import { Target, Sparkles, GraduationCap, + Play, + PenTool, } from 'lucide-react'; import { cn, formatAddress } from '@/lib/utils'; import { useTelegram } from '@/hooks/useTelegram'; @@ -32,6 +34,8 @@ import { useTranslation } from '@/i18n'; import { getAllScores, getStakingScoreStatus, + startScoreTracking, + recordTrustScore, formatDuration, getScoreColor, getScoreRating, @@ -72,6 +76,8 @@ export function RewardsSection() { const [scoresLoading, setScoresLoading] = useState(false); const [citizenshipStatus, setCitizenshipStatus] = useState('NotStarted'); const [showConfirmAnimation, setShowConfirmAnimation] = useState(false); + const [showTrackingAnimation, setShowTrackingAnimation] = useState(false); + const [trackingAnimationText, setTrackingAnimationText] = useState(''); // Check activity status const checkActivityStatus = useCallback(() => { @@ -168,6 +174,52 @@ export function RewardsSection() { } }; + const handleStartTracking = async () => { + if (!peopleApi || !keypair) return; + setTrackingAnimationText(t('rewards.startingTracking')); + setShowTrackingAnimation(true); + hapticImpact('medium'); + try { + const result = await startScoreTracking(peopleApi, keypair); + if (result.success) { + hapticNotification('success'); + showAlert(t('rewards.trackingStarted')); + fetchUserScores(); + } else { + hapticNotification('error'); + showAlert(result.error || t('rewards.trackingFailed')); + } + } catch (err) { + hapticNotification('error'); + showAlert(err instanceof Error ? err.message : t('rewards.trackingFailed')); + } finally { + setShowTrackingAnimation(false); + } + }; + + const handleRecordTrustScore = async () => { + if (!peopleApi || !keypair) return; + setTrackingAnimationText(t('rewards.recordingTrustScore')); + setShowTrackingAnimation(true); + hapticImpact('medium'); + try { + const result = await recordTrustScore(peopleApi, keypair); + if (result.success) { + hapticNotification('success'); + showAlert(t('rewards.trustScoreRecorded')); + fetchUserScores(); + } else { + hapticNotification('error'); + showAlert(result.error || t('rewards.trustScoreRecordFailed')); + } + } catch (err) { + hapticNotification('error'); + showAlert(err instanceof Error ? err.message : t('rewards.trustScoreRecordFailed')); + } finally { + setShowTrackingAnimation(false); + } + }; + const handleActivate = () => { hapticNotification('success'); localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString()); @@ -758,6 +810,37 @@ export function RewardsSection() { + {/* Start Tracking / Record Trust Score Button */} + {!stakingStatus?.isTracking ? ( +
+ +

+ {t('rewards.startTrackingDesc')} +

+
+ ) : ( +
+ +

+ {t('rewards.recordTrustDesc')} +

+
+ )} + {/* Staking Rewards from SubQuery */}

@@ -856,6 +939,15 @@ export function RewardsSection() {

{t('rewards.signingBlockchain')}

)} + + {/* Score Tracking Overlay */} + {showTrackingAnimation && ( +
+ +

{trackingAnimationText}

+

{t('rewards.signingBlockchain')}

+
+ )} ); } diff --git a/src/version.json b/src/version.json index 37662ff..88a2f6f 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.211", - "buildTime": "2026-02-19T22:35:04.294Z", - "buildNumber": 1771540504295 + "version": "1.0.213", + "buildTime": "2026-02-19T23:27:53.391Z", + "buildNumber": 1771543673392 }