rewards: claim what the chain pays, without recording a score first

The People chain has no pezRewards.recordTrustScore and no userEpochScores since the payroll
started holding the roll still through each claim window: a claim pays the trust score at
claim time times the epoch's rate, plus the seat share for a Parlementer seat taken by the
time of the roll. The app still asked for the recorded score, so the claimable list was
always empty, and its Record Trust Score button signed a call that does not exist.

The button, its handler and its texts are removed, and the claimable amount is worked out
as the chain's entitlement does.
This commit is contained in:
Satoshi Qazi Muhammed committed 2026-10-25 19:32:06 +04:00
1 parent 391d446a53
commit df9c96b238
9 files changed
+46 -197

No files matched your search

-6
View File
@@ -140,11 +140,6 @@ const ar: Translations = {
startingTracking: 'جاري بدء التتبع...',
trackingStarted: 'بدأ التتبع!',
trackingFailed: 'فشل بدء التتبع',
recordTrustScore: 'تسجيل نقاط الثقة',
recordTrustDesc: 'سجل نقاطك لمكافآت هذه الفترة',
recordingTrustScore: 'جاري تسجيل نقاط الثقة...',
trustScoreRecorded: 'تم تسجيل نقاط الثقة!',
trustScoreRecordFailed: 'فشل تسجيل النقاط',
citizenCountTitle: 'الأكراد في العالم',
citizenCountDesc: 'المواطنون المسجلون على PezkuwiChain',
beCitizen: 'كن مواطناً',
@@ -153,7 +148,6 @@ const ar: Translations = {
claimPeriod: 'فترة المطالبة',
epochClosed: 'مغلقة',
epochOpen: 'مفتوحة',
scoreRecorded: 'مسجّل',
claimablePez: 'PEZ قابل للمطالبة',
claim: 'طالب',
claimAll: 'طالب بالكل',
-6
View File
@@ -141,11 +141,6 @@ const ckb: Translations = {
startingTracking: 'شوێنکەوتن دەست پێ دەکات...',
trackingStarted: 'شوێنکەوتن دەست پێ کرد!',
trackingFailed: 'دەست پێکردنی شوێنکەوتن سەرنەکەوت',
recordTrustScore: 'خاڵی متمانە تۆمار بکە',
recordTrustDesc: 'خاڵەکەت بۆ خەڵاتەکانی ئەم سەردەمە تۆمار بکە',
recordingTrustScore: 'خاڵ تۆمار دەکرێت...',
trustScoreRecorded: 'خاڵی متمانە تۆمار کرا!',
trustScoreRecordFailed: 'تۆمارکردنی خاڵ سەرنەکەوت',
citizenCountTitle: 'ژمارەی کورد لە جیهان',
citizenCountDesc: 'هاوڵاتییانی تۆمارکراو لە PezkuwiChain',
beCitizen: 'ببە هاوڵاتی',
@@ -154,7 +149,6 @@ const ckb: Translations = {
claimPeriod: 'ماوەی داواکردن',
epochClosed: 'داخراوە',
epochOpen: 'کراوەیە',
scoreRecorded: 'تۆمارکرا',
claimablePez: 'PEZ ی داواکراو',
claim: 'داوا بکە',
claimAll: 'هەموو داوا بکە',
-6
View File
@@ -140,11 +140,6 @@ const en: Translations = {
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',
citizenCountTitle: 'Kurds in the World',
citizenCountDesc: 'Citizens registered on PezkuwiChain',
beCitizen: 'Be Citizen',
@@ -153,7 +148,6 @@ const en: Translations = {
claimPeriod: 'Claim Period',
epochClosed: 'Closed',
epochOpen: 'Open',
scoreRecorded: 'Recorded',
claimablePez: 'Claimable PEZ',
claim: 'Claim',
claimAll: 'Claim All',
-6
View File
@@ -140,11 +140,6 @@ const fa: Translations = {
startingTracking: 'در حال شروع ردیابی...',
trackingStarted: 'ردیابی شروع شد!',
trackingFailed: 'شروع ردیابی ناموفق بود',
recordTrustScore: 'ثبت امتیاز اعتماد',
recordTrustDesc: 'امتیاز خود را برای پاداش‌های این دوره ثبت کنید',
recordingTrustScore: 'در حال ثبت امتیاز اعتماد...',
trustScoreRecorded: 'امتیاز اعتماد ثبت شد!',
trustScoreRecordFailed: 'ثبت امتیاز ناموفق بود',
citizenCountTitle: 'کوردها در جهان',
citizenCountDesc: 'شهروندان ثبت‌شده در PezkuwiChain',
beCitizen: 'شهروند شوید',
@@ -153,7 +148,6 @@ const fa: Translations = {
claimPeriod: 'دوره مطالبه',
epochClosed: 'بسته',
epochOpen: 'باز',
scoreRecorded: 'ثبت شد',
claimablePez: 'PEZ قابل مطالبه',
claim: 'مطالبه',
claimAll: 'مطالبه همه',
-6
View File
@@ -145,11 +145,6 @@ const krd: Translations = {
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',
citizenCountTitle: 'Hejmara Kurd Le Cîhanê',
citizenCountDesc: 'Welatiyên ku li ser PezkuwiChain qeyd bûne',
beCitizen: 'Bibe Welatî',
@@ -158,7 +153,6 @@ const krd: Translations = {
claimPeriod: 'Dema Daxwazkirinê',
epochClosed: 'Girtî',
epochOpen: 'Vekirî',
scoreRecorded: 'Hat tomarkirin',
claimablePez: 'PEZ yên Daxwazkir',
claim: 'Daxwaz Bike',
claimAll: 'Hemûyan Daxwaz Bike',
-6
View File
@@ -140,11 +140,6 @@ const tr: Translations = {
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',
citizenCountTitle: 'Dünyadaki Kürt Sayısı',
citizenCountDesc: "PezkuwiChain'de kayıtlı vatandaşlar",
beCitizen: 'Vatandaş Ol',
@@ -153,7 +148,6 @@ const tr: Translations = {
claimPeriod: 'Talep Dönemi',
epochClosed: 'Kapalı',
epochOpen: 'Açık',
scoreRecorded: 'Kaydedildi',
claimablePez: 'Talep Edilebilir PEZ',
claim: 'Talep Et',
claimAll: 'Tümünü Talep Et',
-6
View File
@@ -142,11 +142,6 @@ export interface Translations {
startingTracking: string;
trackingStarted: string;
trackingFailed: string;
recordTrustScore: string;
recordTrustDesc: string;
recordingTrustScore: string;
trustScoreRecorded: string;
trustScoreRecordFailed: string;
citizenCountTitle: string;
citizenCountDesc: string;
beCitizen: string;
@@ -155,7 +150,6 @@ export interface Translations {
claimPeriod: string;
epochClosed: string;
epochOpen: string;
scoreRecorded: string;
claimablePez: string;
claim: string;
claimAll: string;
+43 -108
View File
@@ -41,8 +41,6 @@ export type EpochStatus = 'Open' | 'ClaimPeriod' | 'Closed';
export interface PezRewardInfo {
currentEpoch: number;
epochStatus: EpochStatus;
hasRecordedThisEpoch: boolean;
userScoreCurrentEpoch: number;
claimableRewards: { epoch: number; amount: string }[];
totalClaimable: string;
hasPendingClaim: boolean;
@@ -508,66 +506,6 @@ export async function startScoreTracking(
}
}
/**
* 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<typeof peopleApi.registry.findMetaError>[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',
};
}
}
// ========================================
// PEZ REWARDS (pezRewards pallet on People Chain)
// ========================================
@@ -616,66 +554,65 @@ export async function getPezRewards(
// Default to Open
}
// Check if user has recorded their score this epoch
let hasRecordedThisEpoch = false;
let userScoreCurrentEpoch = 0;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const userScoreResult = await (peopleApi.query as any).pezRewards.userEpochScores(
currentEpoch,
address
);
if (userScoreResult.isSome) {
hasRecordedThisEpoch = true;
const scoreCodec = userScoreResult.unwrap() as { toString: () => string };
userScoreCurrentEpoch = Number(scoreCodec.toString());
}
} catch {
// User hasn't recorded
}
// Check for claimable rewards from completed epochs
// What a claim pays, worked out the way the chain does (`pezRewards::entitlement`). There
// is no score to record beforehand: the roll is held still from an epoch's finalisation
// to the end of its claim window, so the trust score now is the one the rate was set
// against, and a seat is paid if it is held now and was taken by the time of the roll.
const claimableRewards: { epoch: number; amount: string }[] = [];
let totalClaimable = BigInt(0);
const who = peopleApi.createType('AccountId', address).toString();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query = peopleApi.query as any;
const trustScore = BigInt((await query.trust.trustScores(address)).toString());
// Seat: the Parlementer tiki, not expired, and the date the seat was taken.
let seatTakenAt: number | null = null;
try {
const tikis = ((await query.tiki.userTikis(address)).toJSON() ?? []) as string[];
if (tikis.includes('Parlementer')) {
const expiry = (await query.tiki.tikiExpiry(address, 'Parlementer')).toJSON() as
| number
| null;
const now = (await peopleApi.rpc.chain.getHeader()).number.toNumber();
if (expiry === null || now <= expiry) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const members = (await query.welati.parliamentMembers()).toJSON() as any[];
const member = members.find(
(m) => peopleApi.createType('AccountId', m.account).toString() === who
);
if (member) seatTakenAt = Number(member.electedAt ?? member.elected_at);
}
}
} catch {
// No seat
}
for (let i = Math.max(0, currentEpoch - 3); i < currentEpoch; i++) {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pastStatusResult = await (peopleApi.query as any).pezRewards.epochStatus(i);
const pastStatus = pastStatusResult.toString();
const pastStatus = (await query.pezRewards.epochStatus(i)).toString();
if (pastStatus !== 'ClaimPeriod') continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const claimedResult = await (peopleApi.query as any).pezRewards.claimedRewards(i, address);
const claimedResult = await query.pezRewards.claimedRewards(i, address);
if (claimedResult.isSome) continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const userScoreResult = await (peopleApi.query as any).pezRewards.userEpochScores(
i,
address
);
if (!userScoreResult.isSome) continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const epochPoolResult = await (peopleApi.query as any).pezRewards.epochRewardPools(i);
const epochPoolResult = await query.pezRewards.epochRewardPools(i);
if (!epochPoolResult.isSome) continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const epochPool = (epochPoolResult.unwrap() as any).toJSON();
const userScore = BigInt(
(userScoreResult.unwrap() as { toString: () => string }).toString()
);
const pool = (epochPoolResult.unwrap() as any).toJSON();
const rewardPerPoint = BigInt(
epochPool.rewardPerTrustPoint || epochPool.reward_per_trust_point || '0'
pool.rewardPerTrustPoint ?? pool.reward_per_trust_point ?? '0'
);
const seatShare = BigInt(pool.seatShare ?? pool.seat_share ?? '0');
const finalizedAt = Number(pool.finalizedAt ?? pool.finalized_at ?? 0);
const rewardAmount = userScore * rewardPerPoint;
const rewardFormatted = formatBalancePlanck(rewardAmount.toString());
let rewardAmount = trustScore * rewardPerPoint;
if (seatTakenAt !== null && seatTakenAt <= finalizedAt) rewardAmount += seatShare;
if (rewardAmount === BigInt(0)) continue;
if (parseFloat(rewardFormatted) > 0) {
claimableRewards.push({ epoch: i, amount: rewardFormatted });
totalClaimable += rewardAmount;
}
claimableRewards.push({ epoch: i, amount: formatBalancePlanck(rewardAmount.toString()) });
totalClaimable += rewardAmount;
} catch (err) {
console.warn(`Error checking epoch ${i} rewards:`, err);
}
@@ -684,8 +621,6 @@ export async function getPezRewards(
return {
currentEpoch,
epochStatus,
hasRecordedThisEpoch,
userScoreCurrentEpoch,
claimableRewards,
totalClaimable: formatBalancePlanck(totalClaimable.toString()),
hasPendingClaim: claimableRewards.length > 0,
+3 -47
View File
@@ -22,7 +22,6 @@ import {
Sparkles,
GraduationCap,
Play,
PenTool,
Globe2,
Clock,
Loader2,
@@ -38,7 +37,6 @@ import {
getAllScores,
getStakingScoreStatus,
startScoreTracking,
recordTrustScore,
getPezRewards,
claimPezReward,
formatDuration,
@@ -229,29 +227,6 @@ export function RewardsSection() {
}
};
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 handleClaimReward = async (epochIndex: number) => {
if (!peopleApi || !keypair) return;
setClaimingEpoch(epochIndex);
@@ -1055,7 +1030,8 @@ export function RewardsSection() {
</div>
</div>
{/* Start Tracking / Record Trust Score Button */}
{/* Start Tracking Button. Once tracking, there is nothing to do each month: the
chain holds every score still while an epoch is claimed. */}
{!stakingStatus?.isTracking ? (
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<button
@@ -1070,21 +1046,7 @@ export function RewardsSection() {
{t('rewards.startTrackingDesc')}
</p>
</div>
) : (
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<button
onClick={handleRecordTrustScore}
disabled={!keypair || !peopleApi}
className="w-full py-3 rounded-lg font-medium flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-emerald-600 text-white hover:opacity-90 transition-all disabled:opacity-50"
>
<PenTool className="w-5 h-5" />
{t('rewards.recordTrustScore')}
</button>
<p className="text-xs text-muted-foreground text-center mt-2">
{t('rewards.recordTrustDesc')}
</p>
</div>
)}
) : null}
{/* PEZ Epoch Rewards */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
@@ -1114,12 +1076,6 @@ export function RewardsSection() {
? t('rewards.epochClosed')
: t('rewards.epochOpen')}
</span>
{pezRewards?.hasRecordedThisEpoch && (
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-500/20 text-green-400">
<Check className="w-3 h-3 inline mr-1" />
{t('rewards.scoreRecorded')}
</span>
)}
</div>
{/* Claimable PEZ Rewards */}