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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@cosmjs/tendermint-rpc": "^0.32.1",
"@datadog/browser-logs": "^5.23.3",
"@dydxprotocol/v4-client-js": "3.4.0",
"@dydxprotocol/v4-localization": "1.1.379",
"@dydxprotocol/v4-localization": "1.1.393",
"@dydxprotocol/v4-proto": "^7.0.0-dev.0",
"@emotion/is-prop-valid": "^1.3.0",
"@hugocxl/react-to-image": "^0.0.9",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 98 additions & 12 deletions src/hooks/rewards/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { STRING_KEYS } from '@/constants/localization';

export function pointsToEstimatedDydxRewards(
points?: number,
totalPoints?: number,
Expand All @@ -23,15 +25,104 @@ export function feesToEstimatedDollarRewards(totalFees?: number): number {
return totalFees * CURRENT_SURGE_REWARDS_DETAILS.rebateFraction;
}

type BonkRewardTier = {
positionRange: number[];
reward: number;
};

type BonkRewardsDetails = {
rewards: BonkRewardTier[];
rewardAmount: string;
rewardAmountUsd: number;
topPrizeAmount: string;
startTime: string;
endTime: string;
titleStringKey: string;
leaderboardSize: number;
};

// returns string derived from current time or timestamp in format: Mar | March | March 1
export const simpleDateString = (
timestamp?: string,
options?: {
month?: 'long' | 'short' | undefined;
day?: 'numeric' | undefined;
}
) => {
const date = timestamp ? new Date(timestamp) : new Date();
return date.toLocaleString('en-US', {
...options,
month: options?.month ?? 'long',
timeZone: 'UTC',
});
};

const februaryBonkRewards = [
{ positionRange: [1, 1], reward: 25000 },
{ positionRange: [2, 2], reward: 15000 },
{ positionRange: [3, 3], reward: 10000 },
{ positionRange: [4, 5], reward: 5000 },
{ positionRange: [6, 10], reward: 4000 },
{ positionRange: [11, 20], reward: 2000 },
];

const marchBonkRewards = [
{ positionRange: [1, 1], reward: 15000 },
{ positionRange: [2, 2], reward: 7500 },
{ positionRange: [3, 3], reward: 5000 },
{ positionRange: [4, 5], reward: 2500 },
{ positionRange: [6, 10], reward: 2000 },
{ positionRange: [11, 15], reward: 1500 },
];

const BONK_REWARDS_MAP = {
February: {
rewards: februaryBonkRewards,
rewardAmount: '$100K',
rewardAmountUsd: 100_000,
topPrizeAmount: '$25,000',
leaderboardSize: 20,
startTime: '2026-02-01T00:00:00.000Z',
endTime: '2026-02-28T23:59:59.000Z',
titleStringKey: STRING_KEYS.BONK_PNL_COMPETITION_NAME_FEBRUARY,
},
March: {
rewards: marchBonkRewards,
rewardAmount: '$50k',
rewardAmountUsd: 50_000,
topPrizeAmount: '$15,000',
leaderboardSize: 15,
startTime: '2026-03-01T00:00:00.000Z',
endTime: '2026-03-31T23:59:59.000Z',
titleStringKey: STRING_KEYS.BONK_PNL_COMPETITION_NAME_MARCH,
},
};

const bonkRewardsMonths = Object.keys(BONK_REWARDS_MAP) as (keyof typeof BONK_REWARDS_MAP)[];
const lastBonkRewardsMonth = bonkRewardsMonths[
bonkRewardsMonths.length - 1
] as keyof typeof BONK_REWARDS_MAP;

export const CURRENT_BONK_REWARDS_DETAILS: BonkRewardsDetails =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
BONK_REWARDS_MAP[simpleDateString() as keyof typeof BONK_REWARDS_MAP] ??
BONK_REWARDS_MAP[lastBonkRewardsMonth];

export function positionToBonkRewards(position: number | undefined) {
if (!position) return 0;
if (position === 1) return 25000;
if (position === 2) return 15000;
if (position === 3) return 10000;
if (position === 4 || position === 5) return 5000;
if (position >= 6 && position <= 10) return 4000;
if (position >= 11 && position <= 20) return 2000;
return 0;

const activeBonkRewards = CURRENT_BONK_REWARDS_DETAILS.rewards;

if (!activeBonkRewards.length) return 0;

const activeBonkReward = activeBonkRewards.find(
(reward) =>
!!reward.positionRange[0] &&
!!reward.positionRange[1] &&
position >= reward.positionRange[0] &&
position <= reward.positionRange[1]
);
return activeBonkReward?.reward ?? 0;
}

export const CURRENT_SURGE_REWARDS_DETAILS = {
Expand All @@ -49,11 +140,6 @@ export const LIQUIDATION_REBATES_DETAILS = {
rebateAmountUsd: 1_000_000,
};

export const CURRENT_BONK_REWARDS_DETAILS = {
startTime: '2026-02-01T00:00:00.000Z', // start of february 2026
endTime: '2026-02-28T23:59:59.000Z', // end of february 2026
};

export const DEC_2025_COMPETITION_DETAILS = {
rewardAmount: '$1M',
rewardAmountUsd: 1_000_000,
Expand Down
4 changes: 1 addition & 3 deletions src/hooks/useEnableBonkPnlLeaderboard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { isDev } from '@/constants/networks';
import { StatsigFlags } from '@/constants/statsig';

import { CURRENT_BONK_REWARDS_DETAILS } from './rewards/util';
import { useStatsigGateValue } from './useStatsig';

export const useEnableBonkPnlLeaderboard = () => {
const bonkPnlLeaderboardFF = useStatsigGateValue(StatsigFlags.ffBonkPnlLeaderboard);
const isLive = new Date() >= new Date(CURRENT_BONK_REWARDS_DETAILS.startTime);
return isDev || bonkPnlLeaderboardFF || isLive;
return isDev || bonkPnlLeaderboardFF;
};
77 changes: 63 additions & 14 deletions src/pages/token/BonkIncentivesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
useBonkPnlDistribution,
useFeeLeaderboard,
} from '@/hooks/rewards/hooks';
import { CURRENT_BONK_REWARDS_DETAILS, positionToBonkRewards } from '@/hooks/rewards/util';
import {
CURRENT_BONK_REWARDS_DETAILS,
positionToBonkRewards,
simpleDateString,
} from '@/hooks/rewards/util';
import { useAccounts } from '@/hooks/useAccounts';
import { useNow } from '@/hooks/useNow';
import { useStringGetter } from '@/hooks/useStringGetter';
Expand All @@ -22,7 +26,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output';
import { Panel } from '@/components/Panel';
import { SuccessTag, TagSize } from '@/components/Tag';
import { PrivateTag, SuccessTag, TagSize } from '@/components/Tag';
import { WithTooltip } from '@/components/WithTooltip';

import { useAppDispatch, useAppSelector } from '@/state/appTypes';
Expand All @@ -41,45 +45,90 @@ export const BonkIncentivesPanel = () => {
const BonkIncentivesRewardsPanel = () => {
const stringGetter = useStringGetter();

const { rewardAmount, topPrizeAmount, startTime, endTime, titleStringKey, leaderboardSize } =
CURRENT_BONK_REWARDS_DETAILS;

const isActive = new Date(startTime) <= new Date() && new Date(endTime) >= new Date();

return (
<$Panel>
<div tw="flex gap-3 pb-0.25 pt-0.5">
<div tw="flex flex-1 flex-col gap-1.5">
<div tw="flex flex-col gap-0.5">
<div tw="flex items-center gap-0.5">
<div tw="flex flex-wrap items-center gap-0.5 gap-y-0.25">
<div tw="font-medium-bold">
<span tw="font-bold">
{stringGetter({ key: STRING_KEYS.BONK_REWARDS_HEADLINE })}
{stringGetter({
key: STRING_KEYS.BONK_PNL_REWARDS_HEADLINE,
params: {
COMPETITION_NAME: stringGetter({ key: titleStringKey }),
},
})}
</span>
</div>
<SuccessTag size={TagSize.Medium}>
{stringGetter({ key: STRING_KEYS.ACTIVE })}
</SuccessTag>
{isActive ? (
<SuccessTag size={TagSize.Medium}>
{stringGetter({ key: STRING_KEYS.ACTIVE })}
</SuccessTag>
) : (
<PrivateTag size={TagSize.Medium}>
{stringGetter({ key: STRING_KEYS.INACTIVE })}
</PrivateTag>
)}
</div>
<span>
<span tw="text-color-text-0">
{stringGetter({ key: STRING_KEYS.BONK_REWARDS_BODY })}
{stringGetter({
key: STRING_KEYS.BONK_PNL_REWARDS_BODY,
params: {
REWARD_AMOUNT: rewardAmount,
MONTH: simpleDateString(startTime, { month: 'long' }),
},
})}
</span>
</span>

<div>
<p tw="font-semibold">{stringGetter({ key: STRING_KEYS.BONK_REWARDS_RULES })}</p>
<p tw="font-semibold">{stringGetter({ key: STRING_KEYS.BONK_PNL_REWARDS_RULES })}</p>
<ul tw="list-outside list-disc pl-1.5 text-color-text-0">
<li>{stringGetter({ key: STRING_KEYS.BONK_REWARDS_RULE_1 })}</li>
<li>{stringGetter({ key: STRING_KEYS.BONK_REWARDS_RULE_2 })}</li>
<li>{stringGetter({ key: STRING_KEYS.BONK_REWARDS_RULE_3 })}</li>
<li>
{stringGetter({
key: STRING_KEYS.BONK_PNL_REWARDS_RULE_1,
params: {
MONTH: simpleDateString(startTime, { month: 'long' }),
},
})}
</li>
<li>
{stringGetter({
key: STRING_KEYS.BONK_PNL_REWARDS_RULE_2,
params: {
MONTH_FIRST: `${simpleDateString(startTime, { month: 'short', day: 'numeric' })}`,
MONTH_LAST: `${simpleDateString(endTime, { month: 'short', day: 'numeric' })}`,
},
})}
</li>
<li>
{stringGetter({
key: STRING_KEYS.BONK_PNL_REWARDS_RULE_3,
params: {
LEADERBOARD_SIZE: leaderboardSize,
TOP_PRIZE_AMOUNT: topPrizeAmount,
},
})}
</li>
</ul>
</div>

<span tw="text-color-text-0">
{stringGetter({ key: STRING_KEYS.BONK_REWARDS_BODY_2 })}
{stringGetter({ key: STRING_KEYS.BONK_PNL_REWARDS_BODY_2 })}
</span>
</div>

<div tw="flex items-center gap-0.25 self-start rounded-3 bg-color-layer-1 px-0.875 py-0.5">
<Icon iconName={IconName.Clock} size="1.25rem" tw="text-color-accent" />
<div tw="flex gap-0.375 px-0.375 leading-none">
<MinutesCountdown endTime={CURRENT_BONK_REWARDS_DETAILS.endTime} />
<MinutesCountdown endTime={endTime} />
</div>
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion src/pages/token/RewardsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { STRING_KEYS } from '@/constants/localization';
import { EMPTY_ARR } from '@/constants/objects';
import { AppRoute } from '@/constants/routes';

import { CURRENT_BONK_REWARDS_DETAILS } from '@/hooks/rewards/util';
import { useBreakpoints } from '@/hooks/useBreakpoints';
import { useComplianceState } from '@/hooks/useComplianceState';
import { useEnableBonkPnlLeaderboard } from '@/hooks/useEnableBonkPnlLeaderboard';
Expand Down Expand Up @@ -49,6 +50,7 @@ enum Tab {
}

const RewardsPage = () => {
const { titleStringKey } = CURRENT_BONK_REWARDS_DETAILS;
const stringGetter = useStringGetter();
const navigate = useNavigate();
const enableBonkPnlLeaderboard = useEnableBonkPnlLeaderboard();
Expand Down Expand Up @@ -99,7 +101,7 @@ const RewardsPage = () => {
<BonkPnlPanel />
</div>
),
label: 'BONKuary',
label: stringGetter({ key: titleStringKey }),
value: Tab.BonkPnl,
},
]
Expand Down
Loading