mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-10-27 13:19:49 +00:00
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:
1 parent
ac730481d9
commit
03e6db8461
15 files changed
+57
-351
No files matched your search
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user