Skip to content

Commit c353b83

Browse files
committed
implement rwa competition
1 parent 00faeff commit c353b83

4 files changed

Lines changed: 449 additions & 25 deletions

File tree

src/hooks/rewards/hooks.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,52 @@ export function useBonkPnlLeaderboard() {
175175
};
176176
}
177177

178+
export type RwaMarketPnlItem = {
179+
address: string;
180+
pnl: number;
181+
volume: number;
182+
position: number;
183+
};
184+
185+
type RwaMarketPnlResponse = {
186+
success: boolean;
187+
market: string | null;
188+
week: number | null;
189+
data: RwaMarketPnlItem[];
190+
pagination?: {
191+
total: number;
192+
totalPages: number;
193+
page: number;
194+
perPage: number;
195+
};
196+
};
197+
198+
async function getRwaMarketPnl() {
199+
const res = await fetch(
200+
'https://pp-external-api-ffb2ad95ef03.herokuapp.com/api/dydx-weekly-bonk-market-pnl?perPage=1000'
201+
);
202+
const parsedRes = (await res.json()) as RwaMarketPnlResponse;
203+
return {
204+
data: parsedRes.data,
205+
market: parsedRes.market,
206+
week: parsedRes.week,
207+
};
208+
}
209+
210+
export function useRwaMarketPnl() {
211+
const { data, isLoading } = useQuery({
212+
queryKey: ['rwa-market-pnl'],
213+
queryFn: wrapAndLogError(() => getRwaMarketPnl(), 'RwaMarketPnl/fetch', true),
214+
});
215+
216+
return {
217+
isLoading,
218+
data: data?.data,
219+
market: data?.market ?? null,
220+
week: data?.week ?? null,
221+
};
222+
}
223+
178224
export type LiquidationLeaderboardItem = {
179225
address: string;
180226
total_liquidation_losses: string;

src/hooks/rewards/util.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,47 @@ export const LIQUIDATION_REBATES_DETAILS = {
140140
rebateAmountUsd: 1_000_000,
141141
};
142142

143+
export const RWA_COMPETITION_WEEKS = [
144+
{ week: 1, name: 'Gold Rush', startDate: '2026-04-06T00:00:00.000Z', endDate: '2026-04-13T00:00:00.000Z' },
145+
{ week: 2, name: 'Crude Awakening', startDate: '2026-04-13T00:00:00.000Z', endDate: '2026-04-20T00:00:00.000Z' },
146+
{ week: 3, name: 'Silver Rush', startDate: '2026-04-20T00:00:00.000Z', endDate: '2026-04-27T00:00:00.000Z' },
147+
];
148+
149+
const RWA_REWARDS = [
150+
{ positionRange: [1, 1], reward: 3000 },
151+
{ positionRange: [2, 2], reward: 2000 },
152+
{ positionRange: [3, 3], reward: 1000 },
153+
{ positionRange: [4, 5], reward: 750 },
154+
{ positionRange: [6, 10], reward: 500 },
155+
];
156+
157+
export const RWA_COMPETITION_DETAILS = {
158+
rewards: RWA_REWARDS,
159+
rewardAmount: '$10,000',
160+
rewardAmountUsd: 10_000,
161+
topPrizeAmount: '$3,000',
162+
leaderboardSize: 10,
163+
startTime: '2026-04-06T00:00:00.000Z',
164+
endTime: '2026-04-27T00:00:00.000Z',
165+
};
166+
167+
export function getActiveRwaWeek() {
168+
const now = new Date();
169+
return RWA_COMPETITION_WEEKS.find((week) => {
170+
const start = new Date(week.startDate);
171+
const end = new Date(week.endDate);
172+
return now >= start && now < end;
173+
});
174+
}
175+
176+
export function positionToRwaRewards(position: number | undefined) {
177+
if (!position) return 0;
178+
const reward = RWA_REWARDS.find(
179+
(r) => position >= r.positionRange[0]! && position <= r.positionRange[1]!
180+
);
181+
return reward?.reward ?? 0;
182+
}
183+
143184
export const FEB_2026_COMPETITION_DETAILS = {
144185
rewardAmount: '$1M',
145186
rewardAmountUsd: 1_000_000,

src/pages/token/RewardsPage.tsx

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { EMPTY_ARR } from '@/constants/objects';
1212
import { AppRoute } from '@/constants/routes';
1313
import { timeUnits } from '@/constants/time';
1414

15-
import { CURRENT_BONK_REWARDS_DETAILS } from '@/hooks/rewards/util';
15+
import { getActiveRwaWeek, RWA_COMPETITION_DETAILS } from '@/hooks/rewards/util';
1616
import { useBreakpoints } from '@/hooks/useBreakpoints';
1717
import { useComplianceState } from '@/hooks/useComplianceState';
1818
import { useEnableLiquidationRebates } from '@/hooks/useEnableLiquidationRebates';
@@ -30,10 +30,9 @@ import { TermsOfUseLink } from '@/components/TermsOfUseLink';
3030

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

33-
import { BonkIncentivesPanel } from './BonkIncentivesPanel';
3433
import { BonkPnlLeaderboardPanel } from './BonkPnlLeaderboardPanel';
35-
import { BonkPnlPanel } from './BonkPnlPanel';
3634
import { CompetitionLeaderboardPanel } from './CompetitionLeaderboardPanel';
35+
import { RwaCompetitionPanel } from './RwaCompetitionPanel';
3736
import { GeoblockedPanel } from './GeoblockedPanel';
3837
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
3938
import { LiquidationRebatesHeader } from './LiquidationRebatesHeader';
@@ -45,23 +44,26 @@ import { SwapAndStakingPanel } from './SwapAndStakingPanel';
4544
import { UnbondingPanels } from './UnbondingPanels';
4645

4746
enum Tab {
47+
RwaCompetition = 'RwaCompetition',
4848
Leaderboard = 'Leaderboard',
49-
BonkPnl = 'BonkPnl',
5049
Rewards = 'Rewards',
5150
LiquidationRebates = 'LiquidationRebates',
5251
Competition = 'Competition',
5352
}
5453

5554
const RewardsPage = () => {
56-
const { titleStringKey, endTime } = CURRENT_BONK_REWARDS_DETAILS;
5755
const stringGetter = useStringGetter();
5856
const navigate = useNavigate();
5957
const { complianceState } = useComplianceState();
6058
const { isTablet } = useBreakpoints();
6159
const enableLiquidationRebates = useEnableLiquidationRebates();
6260
const { usdcDenom } = useTokenConfigs();
6361

64-
const [value, setValue] = useState(Tab.Leaderboard);
62+
const [value, setValue] = useState(Tab.RwaCompetition);
63+
64+
const activeWeek = getActiveRwaWeek();
65+
const competitionEnd = new Date(RWA_COMPETITION_DETAILS.endTime);
66+
const endTime = activeWeek?.endDate ?? RWA_COMPETITION_DETAILS.endTime;
6567

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

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

8890
const endMs = new Date(endTime).getTime();
8991
const msRemaining = endMs - Date.now();
90-
const hasEnded = msRemaining <= 0;
92+
const hasEnded = new Date() >= competitionEnd;
9193
const daysRemaining = Math.ceil(msRemaining / timeUnits.day);
92-
const endingSoon = !hasEnded && daysRemaining <= 5;
94+
const endingSoon = !hasEnded && daysRemaining <= 3;
95+
96+
const weekLabel = activeWeek ? `Week ${activeWeek.week}: ${activeWeek.name}` : null;
9397

9498
const tabs = [
9599
{
96-
content: (
97-
<div tw="flexColumn gap-1.5">
98-
<BonkPnlLeaderboardPanel />
99-
</div>
100-
),
101-
label: stringGetter({ key: STRING_KEYS.COMPETITION_LEADERBOARD_TITLE }),
102-
value: Tab.Leaderboard,
103-
},
104-
{
105-
content: (
106-
<div tw="flexColumn gap-1.5">
107-
<BonkIncentivesPanel />
108-
<BonkPnlPanel />
109-
</div>
110-
),
100+
content: <RwaCompetitionPanel />,
111101
label: (
112102
<div tw="flex items-center gap-0.5">
113-
{stringGetter({ key: titleStringKey })}
103+
RWA Trading Competition
114104
{hasEnded ? (
115105
<Tag>{stringGetter({ key: STRING_KEYS.ENDED })}</Tag>
116106
) : endingSoon ? (
117107
<Tag>
118108
{daysRemaining} {daysRemaining === 1 ? 'day' : 'days'} left
119109
</Tag>
110+
) : weekLabel ? (
111+
<Tag>{weekLabel}</Tag>
120112
) : null}
121113
</div>
122114
),
123-
value: Tab.BonkPnl,
115+
value: Tab.RwaCompetition,
116+
},
117+
{
118+
content: (
119+
<div tw="flexColumn gap-1.5">
120+
<BonkPnlLeaderboardPanel />
121+
</div>
122+
),
123+
label: stringGetter({ key: STRING_KEYS.COMPETITION_LEADERBOARD_TITLE }),
124+
value: Tab.Leaderboard,
124125
},
125126
...(enableLiquidationRebates
126127
? [

0 commit comments

Comments
 (0)