diff --git a/package.json b/package.json index 0d7aaea272..81cd3a8394 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c550618ec2..b78d95bc41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ dependencies: specifier: 3.4.0 version: 3.4.0 '@dydxprotocol/v4-localization': - specifier: 1.1.379 - version: 1.1.379 + specifier: 1.1.393 + version: 1.1.393 '@dydxprotocol/v4-proto': specifier: ^7.0.0-dev.0 version: 7.0.5 @@ -1732,8 +1732,8 @@ packages: - utf-8-validate dev: false - /@dydxprotocol/v4-localization@1.1.379: - resolution: {integrity: sha512-/vtaPHxWcmeDjJSI9LNo2GCzdCz9yzmNsDppEivpao0NM0L36SVVzUApM8zqGcWjRI/K6ZWSLmtCBXZaOShkFg==} + /@dydxprotocol/v4-localization@1.1.393: + resolution: {integrity: sha512-z3CUWqyxLHBipFnkHFbODXljZ82gMKtPUulvPVlJXrAiL6fJX5Y1FpI0YHTMWk98jSBDlK8z6TaRtasnwq14Xw==} dev: false /@dydxprotocol/v4-proto@7.0.5: diff --git a/src/hooks/rewards/util.ts b/src/hooks/rewards/util.ts index 44e25dd591..9779e5faca 100644 --- a/src/hooks/rewards/util.ts +++ b/src/hooks/rewards/util.ts @@ -1,3 +1,5 @@ +import { STRING_KEYS } from '@/constants/localization'; + export function pointsToEstimatedDydxRewards( points?: number, totalPoints?: number, @@ -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 = { @@ -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, diff --git a/src/hooks/useEnableBonkPnlLeaderboard.ts b/src/hooks/useEnableBonkPnlLeaderboard.ts index 862d1629f4..83aa82b5db 100644 --- a/src/hooks/useEnableBonkPnlLeaderboard.ts +++ b/src/hooks/useEnableBonkPnlLeaderboard.ts @@ -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; }; diff --git a/src/pages/token/BonkIncentivesPanel.tsx b/src/pages/token/BonkIncentivesPanel.tsx index 0b52c50324..7b23bc232d 100644 --- a/src/pages/token/BonkIncentivesPanel.tsx +++ b/src/pages/token/BonkIncentivesPanel.tsx @@ -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'; @@ -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'; @@ -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>
{stringGetter({ key: STRING_KEYS.BONK_REWARDS_RULES })}
+{stringGetter({ key: STRING_KEYS.BONK_PNL_REWARDS_RULES })}