rewards: claim what the chain pays, without recording a score first (#61)

* 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 three screens offered a Record Trust Score button that signed a call that
does not exist.

The buttons, their handlers and texts, and the messages for an error and a success that can
no longer happen are removed; the claimable amount is worked out as the chain's entitlement
does, and the empty state shows in every epoch status. Past recordTrustScore extrinsics in
the transaction history keep their label.

* backend: body-parser 2.4.0 and ip-address 10.7.5

npm audit on the production dependencies: body-parser 2.0.0-2.3.0 (GHSA-27r3-jxmh-p828,
high, denial of service by trailing bytes after a compressed body) and ip-address <=10.7.3
(GHSA-rxp7-6hxx-x8q5, moderate). Lockfile only; npm audit --omit=dev finds nothing after.

* ops: lower the typecheck baseline to 335

Four TS2339 errors in shared/lib/scores.ts went with the userEpochScores code.

* web: dompurify 3.4.17 and zod 4.6.7

npm audit on the production dependencies: dompurify <=3.4.16 (critical: GHSA-cg2g-xh64-v4q7,
GHSA-mrv2-3pqr-j5jr, GHSA-c6gr-qvv4-5v3f) and zod <4.6.6 (high, GHSA-mrhx-c7xc-m3w5, fixed
only in 4.x). No code imports zod; it is here for abitype, whose peer range is
^3.22.0 || ^4.0.0. Typecheck ratchet, lint and the build are unchanged.
This commit is contained in:
SatoshiQaziMuhammed authored and GitHub committed 2026-10-26 02:37:20 +04:00
1 parent ac730481d9
commit 03e6db8461
15 files changed
+57 -351

No files matched your search

+15 -15
View File
@@ -1005,18 +1005,18 @@
"license": "MIT"
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
"integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^2.0.0",
"content-type": "^2.1.0",
"debug": "^4.4.3",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"iconv-lite": "^0.7.3",
"on-finished": "^2.4.1",
"qs": "^6.15.2",
"qs": "^6.16.1",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
@@ -2048,9 +2048,9 @@
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -2135,9 +2135,9 @@
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
"integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
"version": "10.7.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz",
"integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==",
"license": "MIT",
"optional": true,
"engines": {
@@ -2966,9 +2966,9 @@
}
},
"node_modules/qs": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"version": "6.16.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz",
"integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==",
"license": "BSD-3-Clause",
"dependencies": {
"es-define-property": "^1.0.1",
+1 -1
View File
@@ -3,7 +3,7 @@
3 ../shared/lib/perwerde.ts TS2339
6 ../shared/lib/presale.ts TS2339
1 ../shared/lib/referral.ts TS2339
9 ../shared/lib/scores.ts TS2339
5 ../shared/lib/scores.ts TS2339
3 ../shared/lib/staking.ts TS2339
10 ../shared/lib/validator-pool.ts TS2339
2 ../shared/lib/welati.ts TS2305
-8
View File
@@ -47,10 +47,6 @@ const ERROR_MESSAGES: Record<string, ErrorMessage> = {
en: 'You have already claimed your reward for this epoch.',
kmr: 'We berê xelata xwe ji bo vê epoch-ê wergirtiye.',
},
'pezRewards.NoTrustScoreForEpoch': {
en: 'No trust score recorded for this epoch. You must record your score before claiming.',
kmr: 'Ji bo vê epoch-ê skora emîniyê tomar nebûye. Pêşî divê skora xwe tomar bikî.',
},
'pezRewards.NoRewardToClaim': {
en: 'No reward available to claim for this epoch.',
kmr: 'Ji bo vê epoch-ê xelateke ku were wergirtin tune ye.',
@@ -453,10 +449,6 @@ export const SUCCESS_MESSAGES: Record<string, SuccessMessage> = {
},
// PEZ Rewards
'pezRewards.recorded': {
en: 'Trust score recorded for this epoch. Your score will be used for reward calculation.',
kmr: 'Skora emîniyê ji bo vê epoch-ê tomar bû. Skora we dê ji bo hesabkirina xelatê were bikaranîn.',
},
'pezRewards.claimed': {
en: '{{amount}} PEZ reward claimed successfully!',
kmr: '{{amount}} PEZ xelat bi serkeftî hate wergirtin!',
+26 -65
View File
@@ -45,8 +45,6 @@ export interface EpochRewardPool {
export interface PezRewardInfo {
currentEpoch: number;
epochStatus: EpochStatus;
hasRecordedThisEpoch: boolean;
userScoreCurrentEpoch: number;
claimableRewards: { epoch: number; amount: string }[];
totalClaimable: string;
hasPendingClaim: boolean;
@@ -512,7 +510,6 @@ export async function getPerwerdeScore(
* Uses correct storage query names from live People Chain metadata:
* - epochInfo() → epoch info (ValueQuery)
* - epochStatus(epoch) → Open | ClaimPeriod | Closed
* - userEpochScores(epoch, addr) → user's recorded score (OptionQuery)
* - claimedRewards(epoch, addr) → claimed reward amount (OptionQuery)
* - epochRewardPools(epoch) → reward pool info (OptionQuery)
*/
@@ -548,18 +545,27 @@ export async function getPezRewards(
// Default to Open if query fails
}
// Check if user has recorded their score this epoch
let hasRecordedThisEpoch = false;
let userScoreCurrentEpoch = 0;
// 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 who = peopleApi.createType('AccountId', address).toString();
const trustScore = BigInt((await peopleApi.query.trust.trustScores(address)).toString());
let seatTakenAt: number | null = null;
try {
const userScoreResult = await peopleApi.query.pezRewards.userEpochScores(currentEpoch, address);
if (userScoreResult.isSome) {
hasRecordedThisEpoch = true;
const scoreCodec = userScoreResult.unwrap() as { toString: () => string };
userScoreCurrentEpoch = Number(scoreCodec.toString());
const tikis = ((await peopleApi.query.tiki.userTikis(address)).toJSON() ?? []) as string[];
if (tikis.includes('Parlementer')) {
const expiry = (await peopleApi.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 peopleApi.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 {
// User hasn't recorded
// No seat
}
// Check for claimable rewards from completed epochs
@@ -577,10 +583,6 @@ export async function getPezRewards(
const claimedResult = await peopleApi.query.pezRewards.claimedRewards(i, address);
if (claimedResult.isSome) continue;
// Check if user has a score for this epoch
const userScoreResult = await peopleApi.query.pezRewards.userEpochScores(i, address);
if (!userScoreResult.isSome) continue;
// Get epoch reward pool
const epochPoolResult = await peopleApi.query.pezRewards.epochRewardPools(i);
if (!epochPoolResult.isSome) continue;
@@ -588,17 +590,16 @@ export async function getPezRewards(
const epochPoolCodec = epochPoolResult.unwrap() as { toJSON: () => unknown };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const epochPool = epochPoolCodec.toJSON() as any;
const userScoreCodec = userScoreResult.unwrap() as { toString: () => string };
const userScore = BigInt(userScoreCodec.toString());
const rewardPerPoint = BigInt(epochPool.rewardPerTrustPoint || epochPool.reward_per_trust_point || '0');
const rewardPerPoint = BigInt(epochPool.rewardPerTrustPoint ?? epochPool.reward_per_trust_point ?? '0');
const seatShare = BigInt(epochPool.seatShare ?? epochPool.seat_share ?? '0');
const finalizedAt = Number(epochPool.finalizedAt ?? epochPool.finalized_at ?? 0);
const rewardAmount = userScore * rewardPerPoint;
const rewardFormatted = formatBalance(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: formatBalance(rewardAmount.toString()) });
totalClaimable += rewardAmount;
} catch (err) {
console.warn(`Error checking epoch ${i} rewards:`, err);
}
@@ -607,8 +608,6 @@ export async function getPezRewards(
return {
currentEpoch,
epochStatus,
hasRecordedThisEpoch,
userScoreCurrentEpoch,
claimableRewards,
totalClaimable: formatBalance(totalClaimable.toString()),
hasPendingClaim: claimableRewards.length > 0,
@@ -619,44 +618,6 @@ export async function getPezRewards(
}
}
/**
* Record trust score for the current epoch
* Calls: pezRewards.recordTrustScore()
*/
export async function recordTrustScore(
peopleApi: ApiPromise,
address: string,
signer: any
): Promise<{ success: boolean; error?: string }> {
try {
if (!peopleApi?.tx?.pezRewards?.recordTrustScore) {
return { success: false, error: 'pezRewards pallet not available' };
}
const tx = peopleApi.tx.pezRewards.recordTrustScore();
return new Promise((resolve) => {
tx.signAndSend(address, { signer }, ({ status, dispatchError }: any) => {
if (status.isInBlock || status.isFinalized) {
if (dispatchError) {
if (dispatchError.isModule) {
const decoded = peopleApi.registry.findMetaError(dispatchError.asModule);
resolve({ success: false, error: `${decoded.section}.${decoded.name}` });
} else {
resolve({ success: false, error: dispatchError.toString() });
}
} else {
resolve({ success: true });
}
}
});
});
} catch (error) {
console.error('Error recording trust score:', error);
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* Claim PEZ reward for a specific epoch
* Calls: pezRewards.claimReward(epochIndex)
+8 -8
View File
@@ -55,7 +55,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"dompurify": "^3.4.16",
"dompurify": "^3.4.17",
"embla-carousel-react": "^8.3.0",
"highlight.js": "^11.9.0",
"i18next": "^23.7.6",
@@ -78,7 +78,7 @@
"tailwind-merge": "^2.5.2",
"uuid": "^11.1.0",
"vaul": "^0.9.3",
"zod": "^3.23.8"
"zod": "^4.6.7"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
@@ -7697,9 +7697,9 @@
}
},
"node_modules/dompurify": {
"version": "3.4.16",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -13914,9 +13914,9 @@
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
+2 -2
View File
@@ -68,7 +68,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"dompurify": "^3.4.16",
"dompurify": "^3.4.17",
"embla-carousel-react": "^8.3.0",
"highlight.js": "^11.9.0",
"i18next": "^23.7.6",
@@ -91,7 +91,7 @@
"tailwind-merge": "^2.5.2",
"uuid": "^11.1.0",
"vaul": "^0.9.3",
"zod": "^3.23.8"
"zod": "^4.6.7"
},
"overrides": {
"@pezkuwi/api": "^16.5.39",
@@ -22,7 +22,6 @@ import {
type StakingInfo
} from '@pezkuwi/lib/staking';
import {
recordTrustScore,
claimPezReward,
getPezRewards,
type PezRewardInfo
@@ -48,7 +47,6 @@ export const StakingDashboard: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(false);
const [pezRewards, setPezRewards] = useState<PezRewardInfo | null>(null);
const [isRecordingScore, setIsRecordingScore] = useState(false);
const [isClaimingReward, setIsClaimingReward] = useState(false);
// Fetch staking data from Asset Hub
@@ -111,34 +109,6 @@ export const StakingDashboard: React.FC = () => {
return () => clearInterval(interval);
}, [peopleApi, isPeopleReady, selectedAccount]);
const handleRecordTrustScore = async () => {
if (!peopleApi || !selectedAccount) return;
setIsRecordingScore(true);
try {
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
handleBlockchainSuccess('pezRewards.recorded', toast);
// Refresh PEZ rewards data
setTimeout(async () => {
if (peopleApi && selectedAccount) {
const rewards = await getPezRewards(peopleApi, selectedAccount.address);
setPezRewards(rewards);
}
}, 3000);
} else {
toast.error(result.error || t('staking.recordFailed'));
}
} catch (error) {
if (import.meta.env.DEV) console.error('Record trust score failed:', error);
toast.error(error instanceof Error ? error.message : t('staking.recordFailed'));
} finally {
setIsRecordingScore(false);
}
};
const handleClaimReward = async (epochIndex: number) => {
if (!peopleApi || !selectedAccount) return;
@@ -537,27 +507,6 @@ export const StakingDashboard: React.FC = () => {
<div className="space-y-2">
<p className="text-xs text-gray-500">{t('staking.epoch', { epoch: pezRewards.currentEpoch })}</p>
{/* Open epoch: Record score or show recorded score */}
{pezRewards.epochStatus === 'Open' && (
pezRewards.hasRecordedThisEpoch ? (
<div>
<div className="text-lg font-bold text-green-400">
{t('staking.scoreLabel', { score: pezRewards.userScoreCurrentEpoch })}
</div>
<p className="text-xs text-gray-500">{t('staking.recordedForEpoch')}</p>
</div>
) : (
<Button
size="sm"
onClick={handleRecordTrustScore}
disabled={isRecordingScore}
className="w-full bg-green-600 hover:bg-green-700"
>
{isRecordingScore ? t('staking.recording') : t('staking.recordTrustScore')}
</Button>
)
)}
{/* Claimable rewards */}
{pezRewards.hasPendingClaim && (
<>
-16
View File
@@ -883,10 +883,6 @@ export default {
'staking.epochClaimPeriod': 'فترة المطالبة',
'staking.epochClosed': 'مغلق',
'staking.epoch': 'الحقبة {{epoch}}',
'staking.scoreLabel': 'النقاط: {{score}}',
'staking.recordedForEpoch': 'مسجّل لهذه الحقبة',
'staking.recording': 'جاري التسجيل...',
'staking.recordTrustScore': 'تسجيل نقاط الثقة',
'staking.epochReward': 'الحقبة {{epoch}}: {{amount}} PEZ',
'staking.claim': 'مطالبة',
'staking.description': 'خزّن HEZ لتأمين الشبكة واكسب المكافآت.',
@@ -923,7 +919,6 @@ export default {
'staking.minBondError': 'الحد الأدنى للربط هو {{amount}} HEZ',
'staking.insufficientHez': 'رصيد HEZ غير كافٍ',
'staking.insufficientStaked': 'المبلغ المخزّن غير كافٍ',
'staking.recordFailed': 'فشل في تسجيل نقاط الثقة',
'staking.claimFailed': 'فشل في المطالبة بالمكافأة',
// ValidatorPoolDashboard
@@ -1628,16 +1623,10 @@ export default {
'wallet.epochClaimPeriod': 'فترة المطالبة',
'wallet.epochClosed': 'مغلق',
'wallet.epoch': 'الحقبة {{epoch}} - {{status}}',
'wallet.score': 'النقاط: {{score}}',
'wallet.recordedForEpoch': 'تم التسجيل لهذه الحقبة',
'wallet.recording': 'جاري التسجيل...',
'wallet.recordTrustScore': 'تسجيل نقاط الثقة',
'wallet.epochsToClaim': '{{count}} حقبة للمطالبة',
'wallet.epochReward': 'الحقبة {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'مطالبة',
'wallet.noClaimableRewards': 'لا توجد مكافآت للمطالبة',
'wallet.trustScoreRecorded': 'تم تسجيل نقاط الثقة لهذه الحقبة',
'wallet.failedToRecordScore': 'فشل في تسجيل نقاط الثقة',
'wallet.rewardClaimed': 'تمت المطالبة بمكافأة {{amount}} PEZ!',
'wallet.failedToClaimReward': 'فشل في المطالبة بالمكافأة',
@@ -2314,9 +2303,6 @@ export default {
'dashboard.epochClosed': 'مغلق',
'dashboard.epoch': 'الحقبة {{number}}',
'dashboard.scoreLabel': 'النقاط: {{score}}',
'dashboard.recorded': 'مسجل',
'dashboard.recording': 'جاري التسجيل...',
'dashboard.recordTrustScore': 'سجّل نقاط الثقة',
'dashboard.claim': 'المطالبة',
'dashboard.profileTab': 'الملف الشخصي',
'dashboard.rolesTab': 'الأدوار والتيكي',
@@ -2380,8 +2366,6 @@ export default {
'dashboard.connectWalletError': 'يرجى توصيل محفظتك أولاً',
'dashboard.scoreTrackingStarted': 'بدأ تتبع النقاط! سيتم الآن حساب نقاط التكديس.',
'dashboard.scoreTrackingFailed': 'فشل بدء تتبع النقاط',
'dashboard.trustScoreRecorded': 'تم تسجيل نقاط الثقة لهذه الحقبة.',
'dashboard.trustScoreRecordFailed': 'فشل تسجيل نقاط الثقة',
'dashboard.rewardClaimed': 'تم المطالبة بـ {{amount}} PEZ مكافأة!',
'dashboard.rewardClaimFailed': 'فشل المطالبة بالمكافأة',
'dashboard.noEmailFound': 'لم يتم العثور على عنوان بريد إلكتروني',
-16
View File
@@ -872,10 +872,6 @@ export default {
'staking.epochClaimPeriod': 'ماوەی داواکردن',
'staking.epochClosed': 'داخراوە',
'staking.epoch': 'سەردەم {{epoch}}',
'staking.scoreLabel': 'خاڵ: {{score}}',
'staking.recordedForEpoch': 'بۆ ئەم سەردەمە تۆمارکراوە',
'staking.recording': 'تۆمارکردن...',
'staking.recordTrustScore': 'تۆمارکردنی خاڵی متمانە',
'staking.epochReward': 'سەردەم {{epoch}}: {{amount}} PEZ',
'staking.claim': 'داواکردن',
'staking.title': 'ستەیکینگ',
@@ -913,7 +909,6 @@ export default {
'staking.minBondError': 'کەمترین بەند {{amount}} HEZ',
'staking.insufficientHez': 'باڵانسی HEZ پێ ناگات',
'staking.insufficientStaked': 'بڕی ستەیککراو پێ ناگات',
'staking.recordFailed': 'تۆمارکردنی خاڵی متمانە سەرکەوتوو نەبوو',
'staking.claimFailed': 'داواکردنی پاداشت سەرکەوتوو نەبوو',
// ValidatorPoolDashboard
@@ -1618,16 +1613,10 @@ export default {
'wallet.epochClaimPeriod': 'ماوەی داواکاری',
'wallet.epochClosed': 'داخراوە',
'wallet.epoch': 'سەردەم {{epoch}} - {{status}}',
'wallet.score': 'نمرە: {{score}}',
'wallet.recordedForEpoch': 'بۆ ئەم سەردەمە تۆمارکرا',
'wallet.recording': 'تۆمارکردن...',
'wallet.recordTrustScore': 'نمرەی متمانە تۆمار بکە',
'wallet.epochsToClaim': '{{count}} سەردەم بۆ داواکاری',
'wallet.epochReward': 'سەردەم {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'داواکاری',
'wallet.noClaimableRewards': 'خەڵاتی داواکراو نییە',
'wallet.trustScoreRecorded': 'نمرەی متمانە بۆ ئەم سەردەمە تۆمارکرا',
'wallet.failedToRecordScore': 'تۆمارکردنی نمرەی متمانە سەرکەوتوو نەبوو',
'wallet.rewardClaimed': '{{amount}} PEZ خەڵات داواکرا!',
'wallet.failedToClaimReward': 'داواکردنی خەڵات سەرکەوتوو نەبوو',
@@ -2304,9 +2293,6 @@ export default {
'dashboard.epochClosed': 'داخراوە',
'dashboard.epoch': 'سەردەم {{number}}',
'dashboard.scoreLabel': 'پلە: {{score}}',
'dashboard.recorded': 'تۆمارکرا',
'dashboard.recording': 'تۆماردەکرێت...',
'dashboard.recordTrustScore': 'پلەی متمانە تۆمار بکە',
'dashboard.claim': 'داواکردن',
'dashboard.profileTab': 'پرۆفایل',
'dashboard.rolesTab': 'ڕۆڵ و تیکیەکان',
@@ -2370,8 +2356,6 @@ export default {
'dashboard.connectWalletError': 'تکایە سەرەتا جزدانەکەت پەیوەست بکە',
'dashboard.scoreTrackingStarted': 'شوێنکەوتنی پلە دەستیپێکرد! پلەی ستەیکینگت ئێستا ئەژماردەکرێت.',
'dashboard.scoreTrackingFailed': 'دەستپێکردنی شوێنکەوتنی پلە سەرنەکەوت',
'dashboard.trustScoreRecorded': 'پلەی متمانە بۆ ئەم سەردەمە تۆمارکرا.',
'dashboard.trustScoreRecordFailed': 'تۆمارکردنی پلەی متمانە سەرنەکەوت',
'dashboard.rewardClaimed': '{{amount}} PEZ پاداشت داواکرا!',
'dashboard.rewardClaimFailed': 'داواکردنی پاداشت سەرنەکەوت',
'dashboard.noEmailFound': 'هیچ ناونیشانی ئیمەیڵ نەدۆزرایەوە',
-16
View File
@@ -1216,10 +1216,6 @@ export default {
'staking.epochClaimPeriod': 'Claim Period',
'staking.epochClosed': 'Closed',
'staking.epoch': 'Epoch {{epoch}}',
'staking.scoreLabel': 'Score: {{score}}',
'staking.recordedForEpoch': 'Recorded for this epoch',
'staking.recording': 'Recording...',
'staking.recordTrustScore': 'Record Trust Score',
'staking.epochReward': 'Epoch {{epoch}}: {{amount}} PEZ',
'staking.claim': 'Claim',
'staking.title': 'Staking',
@@ -1257,7 +1253,6 @@ export default {
'staking.minBondError': 'Minimum bond is {{amount}} HEZ',
'staking.insufficientHez': 'Insufficient HEZ balance',
'staking.insufficientStaked': 'Insufficient staked amount',
'staking.recordFailed': 'Failed to record trust score',
'staking.claimFailed': 'Failed to claim reward',
// ValidatorPoolDashboard
@@ -1987,16 +1982,10 @@ export default {
'wallet.epochClaimPeriod': 'Claim Period',
'wallet.epochClosed': 'Closed',
'wallet.epoch': 'Epoch {{epoch}} - {{status}}',
'wallet.score': 'Score: {{score}}',
'wallet.recordedForEpoch': 'Recorded for this epoch',
'wallet.recording': 'Recording...',
'wallet.recordTrustScore': 'Record Trust Score',
'wallet.epochsToClaim': '{{count}} epoch(s) to claim',
'wallet.epochReward': 'Epoch {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'Claim',
'wallet.noClaimableRewards': 'No claimable rewards',
'wallet.trustScoreRecorded': 'Trust score recorded for this epoch',
'wallet.failedToRecordScore': 'Failed to record trust score',
'wallet.rewardClaimed': '{{amount}} PEZ reward claimed!',
'wallet.failedToClaimReward': 'Failed to claim reward',
@@ -2693,9 +2682,6 @@ export default {
'dashboard.epochClosed': 'Closed',
'dashboard.epoch': 'Epoch {{number}}',
'dashboard.scoreLabel': 'Score: {{score}}',
'dashboard.recorded': 'Recorded',
'dashboard.recording': 'Recording...',
'dashboard.recordTrustScore': 'Record Trust Score',
'dashboard.claim': 'Claim',
'dashboard.profileTab': 'Profile',
'dashboard.rolesTab': 'Roles & Tikis',
@@ -2759,8 +2745,6 @@ export default {
'dashboard.connectWalletError': 'Please connect your wallet first',
'dashboard.scoreTrackingStarted': 'Score tracking started! Your staking score will now be calculated.',
'dashboard.scoreTrackingFailed': 'Failed to start score tracking',
'dashboard.trustScoreRecorded': 'Trust score recorded for this epoch.',
'dashboard.trustScoreRecordFailed': 'Failed to record trust score',
'dashboard.rewardClaimed': '{{amount}} PEZ reward claimed!',
'dashboard.rewardClaimFailed': 'Failed to claim reward',
'dashboard.noEmailFound': 'No email address found',
-16
View File
@@ -886,10 +886,6 @@ export default {
'staking.epochClaimPeriod': 'دوره دریافت',
'staking.epochClosed': 'بسته',
'staking.epoch': 'دوره {{epoch}}',
'staking.scoreLabel': 'امتیاز: {{score}}',
'staking.recordedForEpoch': 'برای این دوره ثبت شده',
'staking.recording': 'در حال ثبت...',
'staking.recordTrustScore': 'ثبت امتیاز اعتماد',
'staking.epochReward': 'دوره {{epoch}}: {{amount}} PEZ',
'staking.claim': 'دریافت',
'staking.title': 'سهام‌گذاری',
@@ -927,7 +923,6 @@ export default {
'staking.minBondError': 'حداقل قفل {{amount}} HEZ است',
'staking.insufficientHez': 'موجودی HEZ کافی نیست',
'staking.insufficientStaked': 'مقدار سهام‌گذاری شده کافی نیست',
'staking.recordFailed': 'ثبت امتیاز اعتماد ناموفق بود',
'staking.claimFailed': 'دریافت پاداش ناموفق بود',
// ValidatorPoolDashboard
@@ -1850,16 +1845,10 @@ export default {
'wallet.epochClaimPeriod': 'دوره مطالبه',
'wallet.epochClosed': 'بسته',
'wallet.epoch': 'دوره {{epoch}} - {{status}}',
'wallet.score': 'امتیاز: {{score}}',
'wallet.recordedForEpoch': 'برای این دوره ثبت شد',
'wallet.recording': 'در حال ثبت...',
'wallet.recordTrustScore': 'ثبت امتیاز اعتماد',
'wallet.epochsToClaim': '{{count}} دوره برای مطالبه',
'wallet.epochReward': 'دوره {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'مطالبه',
'wallet.noClaimableRewards': 'پاداش قابل مطالبه‌ای وجود ندارد',
'wallet.trustScoreRecorded': 'امتیاز اعتماد برای این دوره ثبت شد',
'wallet.failedToRecordScore': 'ثبت امتیاز اعتماد ناموفق بود',
'wallet.rewardClaimed': 'پاداش {{amount}} PEZ مطالبه شد!',
'wallet.failedToClaimReward': 'مطالبه پاداش ناموفق بود',
@@ -2348,9 +2337,6 @@ export default {
'dashboard.epochClosed': 'بسته',
'dashboard.epoch': 'دوره {{number}}',
'dashboard.scoreLabel': 'امتیاز: {{score}}',
'dashboard.recorded': 'ثبت شده',
'dashboard.recording': 'در حال ثبت...',
'dashboard.recordTrustScore': 'ثبت امتیاز اعتماد',
'dashboard.claim': 'مطالبه',
'dashboard.profileTab': 'پروفایل',
'dashboard.rolesTab': 'نقش‌ها و تیکی‌ها',
@@ -2414,8 +2400,6 @@ export default {
'dashboard.connectWalletError': 'لطفاً ابتدا کیف پول خود را متصل کنید',
'dashboard.scoreTrackingStarted': 'ردیابی امتیاز شروع شد! امتیاز استیکینگ شما اکنون محاسبه خواهد شد.',
'dashboard.scoreTrackingFailed': 'شروع ردیابی امتیاز ناموفق بود',
'dashboard.trustScoreRecorded': 'امتیاز اعتماد برای این دوره ثبت شد.',
'dashboard.trustScoreRecordFailed': 'ثبت امتیاز اعتماد ناموفق بود',
'dashboard.rewardClaimed': '{{amount}} PEZ پاداش مطالبه شد!',
'dashboard.rewardClaimFailed': 'مطالبه پاداش ناموفق بود',
'dashboard.noEmailFound': 'آدرس ایمیلی یافت نشد',
-16
View File
@@ -880,10 +880,6 @@ export default {
'staking.epochClaimPeriod': 'Serdema Daxwazkirinê',
'staking.epochClosed': 'Girtî',
'staking.epoch': 'Serdem {{epoch}}',
'staking.scoreLabel': 'Pûan: {{score}}',
'staking.recordedForEpoch': 'Ji bo vê serdemê hat tomarkirin',
'staking.recording': 'Tê tomarkirin...',
'staking.recordTrustScore': 'Pûanê Ewlehiyê Tomar Bike',
'staking.epochReward': 'Serdem {{epoch}}: {{amount}} PEZ',
'staking.claim': 'Daxwaz Bike',
'staking.title': 'Staking',
@@ -921,7 +917,6 @@ export default {
'staking.minBondError': 'Girêdana herî kêm {{amount}} HEZ e',
'staking.insufficientHez': 'Hevsengi HEZ bes nîne',
'staking.insufficientStaked': 'Miqdarê stake kirî bes nîne',
'staking.recordFailed': 'Tomarkirina pûanê ewlehiyê bi ser neket',
'staking.claimFailed': 'Daxwazkirina xelatê bi ser neket',
// ValidatorPoolDashboard
@@ -1645,16 +1640,10 @@ export default {
'wallet.epochClaimPeriod': 'Dema Daxwazê',
'wallet.epochClosed': 'Girtî',
'wallet.epoch': 'Serdem {{epoch}} - {{status}}',
'wallet.score': 'Pûan: {{score}}',
'wallet.recordedForEpoch': 'Ji bo vê serdemê hat tomarkirin',
'wallet.recording': 'Tê tomarkirin...',
'wallet.recordTrustScore': 'Pûana Pêbaweriyê Tomar Bike',
'wallet.epochsToClaim': '{{count}} serdem ji bo daxwazê',
'wallet.epochReward': 'Serdem {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'Daxwaz Bike',
'wallet.noClaimableRewards': 'Xelata daxwazkirî tune',
'wallet.trustScoreRecorded': 'Pûana pêbaweriyê ji bo vê serdemê hat tomarkirin',
'wallet.failedToRecordScore': 'Tomarkirina pûana pêbaweriyê bi ser neket',
'wallet.rewardClaimed': '{{amount}} PEZ xelat hat daxwazkirin!',
'wallet.failedToClaimReward': 'Daxwazkirina xelatê bi ser neket',
@@ -2331,9 +2320,6 @@ export default {
'dashboard.epochClosed': 'Girtî',
'dashboard.epoch': 'Serdem {{number}}',
'dashboard.scoreLabel': 'Puan: {{score}}',
'dashboard.recorded': 'Hat tomarkirin',
'dashboard.recording': 'Tê tomarkirin...',
'dashboard.recordTrustScore': 'Puana Pêbaweriyê Tomar Bike',
'dashboard.claim': 'Daxwaz Bike',
'dashboard.profileTab': 'Profîl',
'dashboard.rolesTab': 'Rol û Tikî',
@@ -2397,8 +2383,6 @@ export default {
'dashboard.connectWalletError': 'Ji kerema xwe pêşî wallet-ê xwe girêbidin',
'dashboard.scoreTrackingStarted': 'Şopandina puanê dest pê kir! Puana staking a we niha tê hesibandin.',
'dashboard.scoreTrackingFailed': 'Destpêkirina şopandina puanê biserneket',
'dashboard.trustScoreRecorded': 'Puana pêbaweriyê ji bo vê serdemê hat tomarkirin.',
'dashboard.trustScoreRecordFailed': 'Tomarkirina puana pêbaweriyê biserneket',
'dashboard.rewardClaimed': '{{amount}} PEZ xelat hat daxwaz kirin!',
'dashboard.rewardClaimFailed': 'Daxwazkirina xelatê biserneket',
'dashboard.noEmailFound': 'Tu navnîşana e-mailê nehat dîtin',
-16
View File
@@ -874,10 +874,6 @@ export default {
'staking.epochClaimPeriod': 'Talep Dönemi',
'staking.epochClosed': 'Kapalı',
'staking.epoch': 'Epoch {{epoch}}',
'staking.scoreLabel': 'Puan: {{score}}',
'staking.recordedForEpoch': 'Bu epoch için kaydedildi',
'staking.recording': 'Kaydediliyor...',
'staking.recordTrustScore': 'Güven Puanını Kaydet',
'staking.epochReward': 'Epoch {{epoch}}: {{amount}} PEZ',
'staking.claim': 'Talep Et',
'staking.title': 'Stake Etme',
@@ -915,7 +911,6 @@ export default {
'staking.minBondError': 'Minimum bağlama miktarı {{amount}} HEZ',
'staking.insufficientHez': 'Yetersiz HEZ bakiyesi',
'staking.insufficientStaked': 'Yetersiz stake miktarı',
'staking.recordFailed': 'Güven puanı kaydedilemedi',
'staking.claimFailed': 'Ödül talep edilemedi',
// ValidatorPoolDashboard
@@ -1639,16 +1634,10 @@ export default {
'wallet.epochClaimPeriod': 'Talep Dönemi',
'wallet.epochClosed': 'Kapalı',
'wallet.epoch': 'Dönem {{epoch}} - {{status}}',
'wallet.score': 'Skor: {{score}}',
'wallet.recordedForEpoch': 'Bu dönem için kaydedildi',
'wallet.recording': 'Kaydediliyor...',
'wallet.recordTrustScore': 'Güven Skorunu Kaydet',
'wallet.epochsToClaim': '{{count}} dönem talep edilecek',
'wallet.epochReward': 'Dönem {{epoch}}: {{amount}} PEZ',
'wallet.claim': 'Talep Et',
'wallet.noClaimableRewards': 'Talep edilecek ödül yok',
'wallet.trustScoreRecorded': 'Güven skoru bu dönem için kaydedildi',
'wallet.failedToRecordScore': 'Güven skoru kaydedilemedi',
'wallet.rewardClaimed': '{{amount}} PEZ ödülü talep edildi!',
'wallet.failedToClaimReward': 'Ödül talep edilemedi',
@@ -2332,9 +2321,6 @@ export default {
'dashboard.epochClosed': 'Kapalı',
'dashboard.epoch': 'Dönem {{number}}',
'dashboard.scoreLabel': 'Puan: {{score}}',
'dashboard.recorded': 'Kaydedildi',
'dashboard.recording': 'Kaydediliyor...',
'dashboard.recordTrustScore': 'Güven Puanını Kaydet',
'dashboard.claim': 'Talep Et',
'dashboard.profileTab': 'Profil',
'dashboard.rolesTab': 'Roller ve Tikiler',
@@ -2398,8 +2384,6 @@ export default {
'dashboard.connectWalletError': 'Lütfen önce cüzdanınızı bağlayın',
'dashboard.scoreTrackingStarted': 'Puan takibi başlatıldı! Stake puanınız artık hesaplanacak.',
'dashboard.scoreTrackingFailed': 'Puan takibi başlatılamadı',
'dashboard.trustScoreRecorded': 'Bu dönem için güven puanı kaydedildi.',
'dashboard.trustScoreRecordFailed': 'Güven puanı kaydedilemedi',
'dashboard.rewardClaimed': '{{amount}} PEZ ödül talep edildi!',
'dashboard.rewardClaimFailed': 'Ödül talep edilemedi',
'dashboard.noEmailFound': 'E-posta adresi bulunamadı',
+2 -55
View File
@@ -11,7 +11,7 @@ import { supabase } from '@/lib/supabase';
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp, UserMinus, Play, Loader2, 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, getStakingScoreStatus, startScoreTracking, getPezRewards, recordTrustScore, claimPezReward, type UserScores, type StakingScoreStatus, type PezRewardInfo, formatDuration } from '@pezkuwi/lib/scores';
import { getAllScores, getStakingScoreStatus, startScoreTracking, getPezRewards, claimPezReward, type UserScores, type StakingScoreStatus, type PezRewardInfo, formatDuration } from '@pezkuwi/lib/scores';
import { getSigner } from '@/lib/get-signer';
import { getKycStatus } from '@pezkuwi/lib/kyc';
import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
@@ -41,7 +41,6 @@ export default function Dashboard() {
const [kycStatus, setKycStatus] = useState<string>('NotStarted');
const [renouncingCitizenship, setRenouncingCitizenship] = useState(false);
const [pezRewards, setPezRewards] = useState<PezRewardInfo | null>(null);
const [isRecordingScore, setIsRecordingScore] = useState(false);
const [isClaimingReward, setIsClaimingReward] = useState(false);
const [nftDetails, setNftDetails] = useState<{ citizenNFT: TikiNFTDetails | null; roleNFTs: TikiNFTDetails[]; totalNFTs: number }>({
citizenNFT: null,
@@ -188,27 +187,6 @@ export default function Dashboard() {
}
};
const handleRecordTrustScore = async () => {
if (!peopleApi || !selectedAccount) return;
setIsRecordingScore(true);
try {
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
toast({ title: t('common.success'), description: t('dashboard.trustScoreRecorded') });
fetchScoresAndTikis();
} else {
toast({ title: t('common.error'), description: result.error || t('dashboard.trustScoreRecordFailed'), variant: "destructive" });
}
} catch (error) {
toast({ title: t('common.error'), description: error instanceof Error ? error.message : t('dashboard.trustScoreRecordFailed'), variant: "destructive" });
} finally {
setIsRecordingScore(false);
}
};
const handleClaimReward = async (epochIndex: number) => {
if (!peopleApi || !selectedAccount) return;
@@ -655,35 +633,6 @@ export default function Dashboard() {
<div className="space-y-3">
<p className="text-xs text-muted-foreground">{t('dashboard.epoch', { number: pezRewards.currentEpoch })}</p>
{/* Open epoch: Record score or show recorded score */}
{pezRewards.epochStatus === 'Open' && (
pezRewards.hasRecordedThisEpoch ? (
<div className="flex items-center gap-2">
<div className="text-lg font-bold text-green-600">Score: {pezRewards.userScoreCurrentEpoch}</div>
<Badge variant="outline" className="text-green-600 border-green-300">{t('dashboard.recorded')}</Badge>
</div>
) : (
<Button
size="sm"
className="w-full bg-green-600 hover:bg-green-700"
onClick={handleRecordTrustScore}
disabled={isRecordingScore || loadingScores}
>
{isRecordingScore ? (
<>
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
{t('dashboard.recording')}
</>
) : (
<>
<Play className="h-3 w-3 mr-1" />
{t('dashboard.recordTrustScore')}
</>
)}
</Button>
)
)}
{/* Claimable rewards */}
{pezRewards.hasPendingClaim ? (
<div className="space-y-2">
@@ -706,9 +655,7 @@ export default function Dashboard() {
))}
</div>
) : (
!pezRewards.hasRecordedThisEpoch && pezRewards.epochStatus !== 'Open' && (
<div className="text-2xl font-bold text-muted-foreground">0 PEZ</div>
)
<div className="text-2xl font-bold text-muted-foreground">0 PEZ</div>
)}
</div>
</CardContent>
+3 -50
View File
@@ -8,10 +8,10 @@ import { ReceiveModal } from '@/components/ReceiveModal';
import { TransactionHistory } from '@/components/TransactionHistory';
import { NftList } from '@/components/NftList';
import { Button } from '@/components/ui/button';
import { ArrowUpRight, ArrowDownRight, History, ArrowLeft, RefreshCw, Coins, Loader2 } from 'lucide-react';
import { ArrowUpRight, ArrowDownRight, History, ArrowLeft, RefreshCw, Coins } from 'lucide-react';
import { toast } from 'sonner';
import { getSigner } from '@/lib/get-signer';
import { getPezRewards, recordTrustScore, claimPezReward, type PezRewardInfo } from '@pezkuwi/lib/scores';
import { getPezRewards, claimPezReward, type PezRewardInfo } from '@pezkuwi/lib/scores';
import { isMultisigMember, BRIDGE_MULTISIG_SPECIFIC_ADDRESSES } from '@pezkuwi/lib/multisig';
import { ShieldCheck } from 'lucide-react';
@@ -38,7 +38,6 @@ const WalletDashboard: React.FC = () => {
const [recentTransactions, setRecentTransactions] = useState<Transaction[]>([]);
const [isLoadingRecent, setIsLoadingRecent] = useState(false);
const [pezRewards, setPezRewards] = useState<PezRewardInfo | null>(null);
const [isRecordingScore, setIsRecordingScore] = useState(false);
const [isClaimingReward, setIsClaimingReward] = useState(false);
const [isMultisigSigner, setIsMultisigSigner] = useState(false);
@@ -258,26 +257,6 @@ const WalletDashboard: React.FC = () => {
return () => clearInterval(interval);
}, [peopleApi, isPeopleReady, selectedAccount]);
const handleRecordTrustScore = async () => {
if (!peopleApi || !selectedAccount) return;
setIsRecordingScore(true);
try {
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
toast.success(t('wallet.trustScoreRecorded'));
const rewards = await getPezRewards(peopleApi, selectedAccount.address);
setPezRewards(rewards);
} else {
toast.error(result.error || t('wallet.failedToRecordScore'));
}
} catch (error) {
toast.error(error instanceof Error ? error.message : t('wallet.failedToRecordScore'));
} finally {
setIsRecordingScore(false);
}
};
const handleClaimReward = async (epochIndex: number) => {
if (!peopleApi || !selectedAccount) return;
setIsClaimingReward(true);
@@ -478,30 +457,6 @@ const WalletDashboard: React.FC = () => {
</span>
</div>
{/* Open epoch: Record score */}
{pezRewards.epochStatus === 'Open' && (
pezRewards.hasRecordedThisEpoch ? (
<div className="flex items-center gap-2 mb-3">
<span className="text-green-400 font-semibold">{t('wallet.score', { score: pezRewards.userScoreCurrentEpoch })}</span>
<span className="text-xs text-gray-500">{t('wallet.recordedForEpoch')}</span>
</div>
) : (
<Button
size="sm"
onClick={handleRecordTrustScore}
disabled={isRecordingScore}
className="w-full mb-3 bg-green-600 hover:bg-green-700"
>
{isRecordingScore ? (
<>
<Loader2 className="w-3 h-3 mr-1 animate-spin" />
{t('wallet.recording')}
</>
) : t('wallet.recordTrustScore')}
</Button>
)
)}
{/* Claimable rewards */}
{pezRewards.hasPendingClaim ? (
<div className="space-y-2">
@@ -524,9 +479,7 @@ const WalletDashboard: React.FC = () => {
))}
</div>
) : (
!pezRewards.hasRecordedThisEpoch && pezRewards.epochStatus !== 'Open' && (
<div className="text-gray-500 text-sm">{t('wallet.noClaimableRewards')}</div>
)
<div className="text-gray-500 text-sm">{t('wallet.noClaimableRewards')}</div>
)}
</div>
)}