mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-11-11 05:07:07 +00:00
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:
1 parent
391d446a53
commit
df9c96b238
9 files changed
+46
-197
No files matched your search
@@ -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: 'طالب بالكل',
|
||||
|
||||
@@ -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: 'هەموو داوا بکە',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'مطالبه همه',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in new issue
Block a user