From 2fd21e444a8dae9a432ece1a8891d94b9cb00a15 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 1 May 2026 15:30:56 -0700 Subject: [PATCH] Use user_challenges.amount in claim, not static reward config The /v1/rewards/claim handler was passing the static reward config amount (always 1000 for tt/tut) into both the validator attestation fetch and the EvaluateAttestation Solana instruction, ignoring the per-row amount stored on user_challenges. This breaks rank-dependent payouts. Trending tracks and trending underground winners 6-10 are written with amount=100 in user_challenges (ranks 1-5 keep 1000), but the API was claiming 1000 for everyone - overpaying ranks 6-10 by 10x when claims succeeded, and likely contributing to on-chain failures on retries where prior partial state referenced different amounts. Adds the amount column to GetUndisbursedChallenges, plumbs it through to RewardClaim. The static config amount is no longer consulted for the claim itself; we still call getReward() to validate that the challenge id is configured. Co-Authored-By: Claude Opus 4.7 (1M context) --- api/dbv1/get_undisbursed_challenges.sql.go | 5 ++++- api/dbv1/queries/get_undisbursed_challenges.sql | 3 ++- api/v1_claim_rewards.go | 12 +++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/api/dbv1/get_undisbursed_challenges.sql.go b/api/dbv1/get_undisbursed_challenges.sql.go index a1fb20a9..d4528e34 100644 --- a/api/dbv1/get_undisbursed_challenges.sql.go +++ b/api/dbv1/get_undisbursed_challenges.sql.go @@ -16,7 +16,8 @@ SELECT users.handle, users.wallet, user_challenges.challenge_id, - user_challenges.specifier + user_challenges.specifier, + user_challenges.amount FROM user_challenges JOIN users ON users.user_id = user_challenges.user_id LEFT JOIN challenge_disbursements @@ -41,6 +42,7 @@ type GetUndisbursedChallengesRow struct { Wallet pgtype.Text `json:"wallet"` ChallengeID string `json:"challenge_id"` Specifier string `json:"specifier"` + Amount int32 `json:"amount"` } func (q *Queries) GetUndisbursedChallenges(ctx context.Context, arg GetUndisbursedChallengesParams) ([]GetUndisbursedChallengesRow, error) { @@ -57,6 +59,7 @@ func (q *Queries) GetUndisbursedChallenges(ctx context.Context, arg GetUndisburs &i.Wallet, &i.ChallengeID, &i.Specifier, + &i.Amount, ); err != nil { return nil, err } diff --git a/api/dbv1/queries/get_undisbursed_challenges.sql b/api/dbv1/queries/get_undisbursed_challenges.sql index 84089089..fcb707ef 100644 --- a/api/dbv1/queries/get_undisbursed_challenges.sql +++ b/api/dbv1/queries/get_undisbursed_challenges.sql @@ -3,7 +3,8 @@ SELECT users.handle, users.wallet, user_challenges.challenge_id, - user_challenges.specifier + user_challenges.specifier, + user_challenges.amount FROM user_challenges JOIN users ON users.user_id = user_challenges.user_id LEFT JOIN challenge_disbursements diff --git a/api/v1_claim_rewards.go b/api/v1_claim_rewards.go index 4dc53691..abc6b83b 100644 --- a/api/v1_claim_rewards.go +++ b/api/v1_claim_rewards.go @@ -801,19 +801,25 @@ func (app *ApiServer) v1ClaimRewards(c *fiber.Ctx) error { Specifier: row.Specifier, } - reward, err := getReward(row.ChallengeID, app.rewardAttester.Rewards) + _, err := getReward(row.ChallengeID, app.rewardAttester.Rewards) if err != nil { results[i].Error = err.Error() g.Done() return } - results[i].Amount = reward.Amount + // Use the per-challenge amount from user_challenges (set when the + // challenge was dispatched), not the static config reward.Amount. + // Trending tt/tut challenges pay rank-dependent amounts (e.g. 1000 + // for ranks 1-5 and 100 for ranks 6-10) that the static reward + // config can't represent. + amount := uint64(row.Amount) + results[i].Amount = amount rewardClaim := RewardClaim{ RewardClaim: rewards.RewardClaim{ RewardID: row.ChallengeID, - Amount: reward.Amount, + Amount: amount, Specifier: row.Specifier, RecipientEthAddress: row.Wallet.String, ClaimAuthority: antiAbuseOracle.DelegateWallet,