Skip to content
Merged
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
46 changes: 46 additions & 0 deletions src/hooks/rewards/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,52 @@ export function useBonkPnlLeaderboard() {
};
}

export type RwaMarketPnlItem = {
address: string;
pnl: number;
volume: number;
position: number;
};

type RwaMarketPnlResponse = {
success: boolean;
market: string | null;
week: number | null;
data: RwaMarketPnlItem[];
pagination?: {
total: number;
totalPages: number;
page: number;
perPage: number;
};
};

async function getRwaMarketPnl() {
const res = await fetch(
'https://pp-external-api-ffb2ad95ef03.herokuapp.com/api/dydx-weekly-bonk-market-pnl?perPage=1000'
);
const parsedRes = (await res.json()) as RwaMarketPnlResponse;
return {
data: parsedRes.data,
market: parsedRes.market,
week: parsedRes.week,
};
}

export function useRwaMarketPnl() {
const { data, isLoading } = useQuery({
queryKey: ['rwa-market-pnl'],
queryFn: wrapAndLogError(() => getRwaMarketPnl(), 'RwaMarketPnl/fetch', true),
});

return {
isLoading,
data: data?.data,
market: data?.market ?? null,
week: data?.week ?? null,
};
}

export type LiquidationLeaderboardItem = {
address: string;
total_liquidation_losses: string;
Expand Down
41 changes: 41 additions & 0 deletions src/hooks/rewards/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,47 @@ export const LIQUIDATION_REBATES_DETAILS = {
rebateAmountUsd: 1_000_000,
};

export const RWA_COMPETITION_WEEKS = [
{ week: 1, name: 'Gold Rush', startDate: '2026-04-06T00:00:00.000Z', endDate: '2026-04-13T00:00:00.000Z' },
{ week: 2, name: 'Crude Awakening', startDate: '2026-04-13T00:00:00.000Z', endDate: '2026-04-20T00:00:00.000Z' },
{ week: 3, name: 'Silver Rush', startDate: '2026-04-20T00:00:00.000Z', endDate: '2026-04-27T00:00:00.000Z' },
];

const RWA_REWARDS = [
{ positionRange: [1, 1], reward: 3000 },
{ positionRange: [2, 2], reward: 2000 },
{ positionRange: [3, 3], reward: 1000 },
{ positionRange: [4, 5], reward: 750 },
{ positionRange: [6, 10], reward: 500 },
];

export const RWA_COMPETITION_DETAILS = {
rewards: RWA_REWARDS,
rewardAmount: '$10,000',
rewardAmountUsd: 10_000,
topPrizeAmount: '$3,000',
leaderboardSize: 10,
startTime: '2026-04-06T00:00:00.000Z',
endTime: '2026-04-27T00:00:00.000Z',
};

export function getActiveRwaWeek() {
const now = new Date();
return RWA_COMPETITION_WEEKS.find((week) => {
const start = new Date(week.startDate);
const end = new Date(week.endDate);
return now >= start && now < end;
});
}

export function positionToRwaRewards(position: number | undefined) {
if (!position) return 0;
const reward = RWA_REWARDS.find(
(r) => position >= r.positionRange[0]! && position <= r.positionRange[1]!
);
return reward?.reward ?? 0;
}

export const FEB_2026_COMPETITION_DETAILS = {
rewardAmount: '$1M',
rewardAmountUsd: 1_000_000,
Expand Down
51 changes: 26 additions & 25 deletions src/pages/token/RewardsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EMPTY_ARR } from '@/constants/objects';
import { AppRoute } from '@/constants/routes';
import { timeUnits } from '@/constants/time';

import { CURRENT_BONK_REWARDS_DETAILS } from '@/hooks/rewards/util';
import { getActiveRwaWeek, RWA_COMPETITION_DETAILS } from '@/hooks/rewards/util';
import { useBreakpoints } from '@/hooks/useBreakpoints';
import { useComplianceState } from '@/hooks/useComplianceState';
import { useEnableLiquidationRebates } from '@/hooks/useEnableLiquidationRebates';
Expand All @@ -30,10 +30,9 @@ import { TermsOfUseLink } from '@/components/TermsOfUseLink';

import { orEmptyObj } from '@/lib/typeUtils';

import { BonkIncentivesPanel } from './BonkIncentivesPanel';
import { BonkPnlLeaderboardPanel } from './BonkPnlLeaderboardPanel';
import { BonkPnlPanel } from './BonkPnlPanel';
import { CompetitionLeaderboardPanel } from './CompetitionLeaderboardPanel';
import { RwaCompetitionPanel } from './RwaCompetitionPanel';
import { GeoblockedPanel } from './GeoblockedPanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { LiquidationRebatesHeader } from './LiquidationRebatesHeader';
Expand All @@ -45,23 +44,26 @@ import { SwapAndStakingPanel } from './SwapAndStakingPanel';
import { UnbondingPanels } from './UnbondingPanels';

enum Tab {
RwaCompetition = 'RwaCompetition',
Leaderboard = 'Leaderboard',
BonkPnl = 'BonkPnl',
Rewards = 'Rewards',
LiquidationRebates = 'LiquidationRebates',
Competition = 'Competition',
}

const RewardsPage = () => {
const { titleStringKey, endTime } = CURRENT_BONK_REWARDS_DETAILS;
const stringGetter = useStringGetter();
const navigate = useNavigate();
const { complianceState } = useComplianceState();
const { isTablet } = useBreakpoints();
const enableLiquidationRebates = useEnableLiquidationRebates();
const { usdcDenom } = useTokenConfigs();

const [value, setValue] = useState(Tab.Leaderboard);
const [value, setValue] = useState(Tab.RwaCompetition);

const activeWeek = getActiveRwaWeek();
const competitionEnd = new Date(RWA_COMPETITION_DETAILS.endTime);
const endTime = activeWeek?.endDate ?? RWA_COMPETITION_DETAILS.endTime;

const { totalRewards } = orEmptyObj(BonsaiHooks.useStakingRewards().data);

Expand All @@ -87,40 +89,39 @@ const RewardsPage = () => {

const endMs = new Date(endTime).getTime();
const msRemaining = endMs - Date.now();
const hasEnded = msRemaining <= 0;
const hasEnded = new Date() >= competitionEnd;
const daysRemaining = Math.ceil(msRemaining / timeUnits.day);
const endingSoon = !hasEnded && daysRemaining <= 5;
const endingSoon = !hasEnded && daysRemaining <= 3;

const weekLabel = activeWeek ? `Week ${activeWeek.week}: ${activeWeek.name}` : null;

const tabs = [
{
content: (
<div tw="flexColumn gap-1.5">
<BonkPnlLeaderboardPanel />
</div>
),
label: stringGetter({ key: STRING_KEYS.COMPETITION_LEADERBOARD_TITLE }),
value: Tab.Leaderboard,
},
{
content: (
<div tw="flexColumn gap-1.5">
<BonkIncentivesPanel />
<BonkPnlPanel />
</div>
),
content: <RwaCompetitionPanel />,
label: (
<div tw="flex items-center gap-0.5">
{stringGetter({ key: titleStringKey })}
RWA Trading Competition
{hasEnded ? (
<Tag>{stringGetter({ key: STRING_KEYS.ENDED })}</Tag>
) : endingSoon ? (
<Tag>
{daysRemaining} {daysRemaining === 1 ? 'day' : 'days'} left
</Tag>
) : weekLabel ? (
<Tag>{weekLabel}</Tag>
) : null}
</div>
),
value: Tab.BonkPnl,
value: Tab.RwaCompetition,
},
{
content: (
<div tw="flexColumn gap-1.5">
<BonkPnlLeaderboardPanel />
</div>
),
label: stringGetter({ key: STRING_KEYS.COMPETITION_LEADERBOARD_TITLE }),
value: Tab.Leaderboard,
},
...(enableLiquidationRebates
? [
Expand Down
Loading
Loading