Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/comps/components/nfl/brier-score-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function BrierScoreChart({
} = useNflPredictions(gameId ? competitionId : undefined, gameId);

const { chartData, agentIds, agentLabels } = useMemo(() => {
if (!game || !predictionsData?.predictions?.length) {
if (!game || !predictionsData?.length) {
return {
chartData: [] as TimelinePoint[],
agentIds: [] as string[],
Expand All @@ -112,7 +112,7 @@ export function BrierScoreChart({
}

const agentLabelsMap = new Map<string, string>();
predictionsData.predictions.forEach((prediction) => {
predictionsData.forEach((prediction) => {
if (!agentLabelsMap.has(prediction.agentId)) {
agentLabelsMap.set(
prediction.agentId,
Expand All @@ -121,10 +121,10 @@ export function BrierScoreChart({
}
});

const predictionsWithTimestamps = predictionsData.predictions
const predictionsWithTimestamps = predictionsData
.map((prediction) => ({
...prediction,
timestamp: new Date(prediction.createdAt).getTime(),
timestamp: prediction.createdAt.getTime(),
}))
.sort((a, b) => a.timestamp - b.timestamp);

Expand Down
2 changes: 1 addition & 1 deletion apps/comps/components/nfl/nfl-standings-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function NflStandingsTable({

games.forEach((game, index) => {
const predictionResult = predictionQueries[index];
const predictions = predictionResult?.data?.predictions ?? [];
const predictions = predictionResult?.data ?? [];
const agentPredictionMap = new Map<
string,
{ predictedWinner: string; confidence: number }
Expand Down
4 changes: 2 additions & 2 deletions apps/comps/components/nfl/predictions-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function PredictionsTable({
if (error) {
return <div className="text-destructive">Failed to load predictions</div>;
}
if (!data?.predictions || data.predictions.length === 0) {
if (!data || data.length === 0) {
return (
<div className="flex h-[360px] items-center justify-center">
<p className="text-sm text-gray-400">
Expand All @@ -66,7 +66,7 @@ export function PredictionsTable({
<div className="col-span-1 text-right">Reason</div>
</div>

{data.predictions.map((prediction: NflPrediction) => {
{data.map((prediction: NflPrediction) => {
const agentLabel =
prediction.agentName ?? `${prediction.agentId.slice(0, 8)}...`;

Expand Down
139 changes: 29 additions & 110 deletions apps/comps/hooks/useNextSeasonEligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,14 @@ import { useAccount } from "wagmi";
import { attoValueToNumberValue } from "@recallnet/conversions/atto-conversions";

import { tanstackClient } from "@/rpc/clients/tanstack-query";
import type { RouterOutputs } from "@/rpc/router";
import { formatAmount } from "@/utils/format";

/**
* Response type from the getNextAirdropEligibility RPC
*/
export interface NextAirdropEligibilityResponse {
isEligible: boolean;
airdrop: number;
activitySeason: {
number: number;
name: string;
startDate: string;
endDate: string;
};
activeStake: string;
potentialReward: string;
eligibilityReasons: {
hasBoostedAgents: boolean;
hasCompetedInCompetitions: boolean;
boostedCompetitionIds: string[];
competedCompetitionIds: string[];
totalUniqueCompetitions: number;
};
poolStats: {
totalActiveStakes: string;
availableRewardsPool: string;
totalForfeited: string;
totalAlreadyClaimed: string;
};
minCompetitionsRequired: number;
}

/**
* Formatted eligibility data for UI display
*/
export interface FormattedEligibilityData {
isEligible: boolean;
isAlmostEligible: boolean;
airdrop: number;
activitySeasonNumber: number;
activitySeasonName: string;
activitySeasonStartDate: Date;
activitySeasonEndDate: Date;
daysRemainingInActivitySeason: number;
activeStake: bigint;
activeStakeFormatted: string;
potentialReward: bigint;
potentialRewardFormatted: string;
hasStaked: boolean;
hasBoostedOrCompeted: boolean;
hasBoostedAgents: boolean;
hasCompetedInCompetitions: boolean;
/** Competition IDs where the user boosted agents */
boostedCompetitionIds: string[];
/** Competition IDs where the user's agents competed */
competedCompetitionIds: string[];
/** Total count of unique competitions (boosted + competed combined) */
totalUniqueCompetitions: number;
/** Minimum number of competitions required for eligibility */
minCompetitionsRequired: number;
/** How many more competitions are needed to become eligible */
competitionsRemaining: number;
totalActiveStakes: bigint;
totalActiveStakesFormatted: string;
availableRewardsPool: bigint;
availableRewardsPoolFormatted: string;
totalForfeited: bigint;
totalForfeitedFormatted: string;
}
type NextAirdropEligibilityResponse =
RouterOutputs["airdrop"]["getNextAirdropEligibility"];

/**
* Calculate whole days remaining until a target date.
Expand All @@ -90,21 +29,10 @@ function getDaysRemaining(endDate: Date, now: Date = new Date()): number {
}

/**
* Hook result type
* Format bigint value to display format
*/
export interface UseNextSeasonEligibilityResult {
data: FormattedEligibilityData | null;
isLoading: boolean;
error: Error | null;
refetch: () => void;
}

/**
* Format bigint value from string to display format
*/
function formatBigintString(value: string, compact: boolean = true): string {
const bigintValue = BigInt(value);
const numberValue = attoValueToNumberValue(bigintValue);
function formatBigintValue(value: bigint, compact: boolean = true): string {
const numberValue = attoValueToNumberValue(value);
if (!numberValue || numberValue === 0) {
return "0";
}
Expand All @@ -114,7 +42,7 @@ function formatBigintString(value: string, compact: boolean = true): string {
/**
* Hook for fetching and formatting next season eligibility data
*/
export function useNextSeasonEligibility(): UseNextSeasonEligibilityResult {
export function useNextSeasonEligibility() {
const { address } = useAccount();

const {
Expand All @@ -129,25 +57,21 @@ export function useNextSeasonEligibility(): UseNextSeasonEligibilityResult {
}),
);

const formattedData = useMemo<FormattedEligibilityData | null>(() => {
const formattedData = useMemo(() => {
if (!rawData) return null;

const activitySeasonStartDate = new Date(rawData.activitySeason.startDate);
const activitySeasonEndDate = new Date(rawData.activitySeason.endDate);

const activeStake = BigInt(rawData.activeStake);
const potentialReward = BigInt(rawData.potentialReward);
const totalActiveStakes = BigInt(rawData.poolStats.totalActiveStakes);
const availableRewardsPool = BigInt(rawData.poolStats.availableRewardsPool);
const totalForfeited = BigInt(rawData.poolStats.totalForfeited);
const { activeStake, potentialReward, poolStats, eligibilityReasons } =
rawData;
const { totalActiveStakes, availableRewardsPool, totalForfeited } =
poolStats;
const { totalUniqueCompetitions } = eligibilityReasons;
const { minCompetitionsRequired } = rawData;

const hasStaked = activeStake > 0n;
const hasBoostedOrCompeted =
rawData.eligibilityReasons.hasBoostedAgents ||
rawData.eligibilityReasons.hasCompetedInCompetitions;
eligibilityReasons.hasBoostedAgents ||
eligibilityReasons.hasCompetedInCompetitions;

const { totalUniqueCompetitions } = rawData.eligibilityReasons;
const { minCompetitionsRequired } = rawData;
const competitionsRemaining = Math.max(
0,
minCompetitionsRequired - totalUniqueCompetitions,
Expand All @@ -167,35 +91,30 @@ export function useNextSeasonEligibility(): UseNextSeasonEligibilityResult {
airdrop: rawData.airdrop,
activitySeasonNumber: rawData.activitySeason.number,
activitySeasonName: rawData.activitySeason.name,
activitySeasonStartDate,
activitySeasonEndDate,
activitySeasonStartDate: rawData.activitySeason.startDate,
activitySeasonEndDate: rawData.activitySeason.endDate,
activeStake,
activeStakeFormatted: formatBigintString(rawData.activeStake),
activeStakeFormatted: formatBigintValue(activeStake),
potentialReward,
potentialRewardFormatted: formatBigintString(rawData.potentialReward),
potentialRewardFormatted: formatBigintValue(potentialReward),
hasStaked,
hasBoostedOrCompeted,
hasBoostedAgents: rawData.eligibilityReasons.hasBoostedAgents,
hasCompetedInCompetitions:
rawData.eligibilityReasons.hasCompetedInCompetitions,
boostedCompetitionIds: rawData.eligibilityReasons.boostedCompetitionIds,
competedCompetitionIds: rawData.eligibilityReasons.competedCompetitionIds,
hasBoostedAgents: eligibilityReasons.hasBoostedAgents,
hasCompetedInCompetitions: eligibilityReasons.hasCompetedInCompetitions,
boostedCompetitionIds: eligibilityReasons.boostedCompetitionIds,
competedCompetitionIds: eligibilityReasons.competedCompetitionIds,
totalUniqueCompetitions,
minCompetitionsRequired,
competitionsRemaining,
totalActiveStakes,
totalActiveStakesFormatted: formatBigintString(
rawData.poolStats.totalActiveStakes,
),
totalActiveStakesFormatted: formatBigintValue(totalActiveStakes),
availableRewardsPool,
availableRewardsPoolFormatted: formatBigintString(
rawData.poolStats.availableRewardsPool,
),
availableRewardsPoolFormatted: formatBigintValue(availableRewardsPool),
totalForfeited,
totalForfeitedFormatted: formatBigintString(
rawData.poolStats.totalForfeited,
totalForfeitedFormatted: formatBigintValue(totalForfeited),
daysRemainingInActivitySeason: getDaysRemaining(
rawData.activitySeason.endDate,
),
daysRemainingInActivitySeason: getDaysRemaining(activitySeasonEndDate),
};
}, [rawData]);

Expand Down
2 changes: 1 addition & 1 deletion apps/comps/rpc/router/admin/bonus-boost/revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const revokeBonusBoost = adminBase
keptCount: result.keptInCompetitions.length,
id: result.boostBonusId,
revoked: result.revoked,
revokedAt: result.revokedAt.toISOString(),
revokedAt: result.revokedAt,
removedFromCompetitions: result.removedFromCompetitions,
keptInCompetitions: result.keptInCompetitions,
}));
Expand Down
31 changes: 1 addition & 30 deletions apps/comps/rpc/router/airdrop/get-next-season-eligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,8 @@ export const getNextAirdropEligibility = base
input.airdrop,
);

// Convert bigint values to strings for JSON serialization
return {
isEligible: eligibility.isEligible,
airdrop: eligibility.airdrop,
activitySeason: {
number: eligibility.activitySeason.number,
name: eligibility.activitySeason.name,
startDate: eligibility.activitySeason.startDate.toISOString(),
endDate: eligibility.activitySeason.endDate.toISOString(),
},
activeStake: eligibility.activeStake.toString(),
potentialReward: eligibility.potentialReward.toString(),
eligibilityReasons: {
hasBoostedAgents: eligibility.eligibilityReasons.hasBoostedAgents,
hasCompetedInCompetitions:
eligibility.eligibilityReasons.hasCompetedInCompetitions,
boostedCompetitionIds:
eligibility.eligibilityReasons.boostedCompetitionIds,
competedCompetitionIds:
eligibility.eligibilityReasons.competedCompetitionIds,
totalUniqueCompetitions:
eligibility.eligibilityReasons.totalUniqueCompetitions,
},
poolStats: {
totalActiveStakes: eligibility.poolStats.totalActiveStakes.toString(),
availableRewardsPool:
eligibility.poolStats.availableRewardsPool.toString(),
totalForfeited: eligibility.poolStats.totalForfeited.toString(),
totalAlreadyClaimed:
eligibility.poolStats.totalAlreadyClaimed.toString(),
},
...eligibility,
minCompetitionsRequired: config.airdrop.minCompetitionsForEligibility,
};
} catch (error) {
Expand Down
20 changes: 1 addition & 19 deletions apps/comps/rpc/router/nfl/get-game-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,7 @@ export const getGameInfo = base
throw errors.NOT_FOUND({ message: "Game not found" });
}

return {
game: {
id: game.id,
globalGameId: game.providerGameId,
season: game.season,
week: game.week,
startTime: game.startTime.toISOString(),
endTime: game.endTime?.toISOString() || null,
homeTeam: game.homeTeam,
awayTeam: game.awayTeam,
spread: game.spread,
overUnder: game.overUnder,
awayTeamMoneyLine: game.awayTeamMoneyLine,
homeTeamMoneyLine: game.homeTeamMoneyLine,
venue: game.venue,
status: game.status,
winner: game.winner,
},
};
return game;

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return structure has changed from an object with a game property to returning the game object directly. This is a breaking change that will cause existing E2E tests to fail. The tests in apps/comps/e2e/tests/nfl-competition.test.ts expect to access data.game (lines 664, 852, 853), but this change makes the game object the top-level return value.

If this breaking change is intentional, the E2E tests need to be updated accordingly. If not, the return structure should be preserved as { game } for backward compatibility.

Suggested change
return game;
return { game };

Copilot uses AI. Check for mistakes.
} catch (error) {
if (error instanceof ORPCError) {
throw error;
Expand Down
12 changes: 1 addition & 11 deletions apps/comps/rpc/router/nfl/get-predictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,7 @@ export const getPredictions = base
input.competitionId,
);

return {
predictions: predictions.map((p) => ({
id: p.id,
agentId: p.agentId,
agentName: p.agentName ?? null,
predictedWinner: p.predictedWinner,
confidence: p.confidence,
reason: p.reason,
createdAt: p.createdAt.toISOString(),
})),
};
return predictions;

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return structure has changed from an object with a predictions property to returning the array directly. This is a breaking change that will cause existing E2E tests to fail. The tests in apps/comps/e2e/tests/nfl-competition.test.ts expect to access data.predictions (lines 682, 683, 899, 900, 914), but this change makes the array the top-level return value.

If this breaking change is intentional, the E2E tests need to be updated accordingly. If not, the return structure should be preserved as { predictions } for backward compatibility.

Suggested change
return predictions;
return { predictions };

Copilot uses AI. Check for mistakes.
} catch (error) {
if (error instanceof ORPCError) {
throw error;
Expand Down
2 changes: 1 addition & 1 deletion apps/comps/types/nfl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface NflPrediction
SelectGamePrediction,
"id" | "agentId" | "predictedWinner" | "confidence"
> {
createdAt: string;
createdAt: Date;
reason?: string | null;
agentName?: string | null;
}
Expand Down