Skip to content

Commit 90322ac

Browse files
VGR-GITclaude
andauthored
chore(rewards): perps campaign remove margin stat (#29911)
## **Description** Removes the margin requirement from the Perps Trading Campaign rewards qualification UI to match the latest design. Qualification is now driven solely by notional volume (≥ $25k); the `marginDeployed` field is dropped from DTOs, cached state, controller cache read/write, locale keys, and tests. On the stats screen PnL and Volume now share one row, and the summary card replaces the margin StatCell with a spacer so Volume stays left-aligned. ## **Changelog** CHANGELOG entry: null ## **Screenshots/Recordings** - Active campaign, have positions <img width="305" height="176" alt="image" src="https://github.com/user-attachments/assets/9738b8f3-9ef1-4c33-8a92-9b3a94e2e940" /> <img width="313" height="280" alt="image" src="https://github.com/user-attachments/assets/7b151b6f-4b79-4be7-b9a4-24bf13cfc3a5" /> - Completed campaign, have positions <img width="313" height="280" alt="image" src="https://github.com/user-attachments/assets/c50271b7-6592-45b2-842f-45adb3155f02" /> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it removes the `marginDeployed` field from Perps leaderboard position DTO/state and updates controller caching logic, which could break consumers expecting that data or older cached shapes. > > **Overview** > Updates the Perps Trading Campaign flow to **drop the margin-deployed metric and requirement**, making qualification driven solely by notional volume. > > This removes `marginDeployed` from the Perps leaderboard position DTO and cached state, updates `RewardsController` cache read/write accordingly, and simplifies the stats/summary UI to no longer render a margin StatCell (using a spacer to preserve layout). Tests and locale strings are updated to match the new three-stat display and revised qualification semantics. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 855c01b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e8c8963 commit 90322ac

28 files changed

Lines changed: 11 additions & 88 deletions

app/components/UI/Rewards/Views/PerpsTradingCampaignDetailsView.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ function toMockLeaderboardPosition(
348348
rank: position.rank,
349349
pnl: 0,
350350
notionalVolume: 0,
351-
marginDeployed: 0,
352351
qualified: true,
353352
neighbors: position.neighbors as PerpsTradingCampaignLeaderboardEntry[],
354353
computedAt: '2025-08-15T12:00:00.000Z',

app/components/UI/Rewards/Views/PerpsTradingCampaignLeaderboardView.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ const basePosition: PerpsTradingCampaignLeaderboardPositionDto = {
149149
rank: 4,
150150
pnl: 1000,
151151
notionalVolume: 10_000,
152-
marginDeployed: 2000,
153152
qualified: true,
154153
neighbors: [],
155154
computedAt: '2025-01-01T00:00:00.000Z',

app/components/UI/Rewards/Views/PerpsTradingCampaignStatsView.test.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ const basePosition: PerpsTradingCampaignLeaderboardPositionDto = {
173173
rank: 4,
174174
pnl: 1500.25,
175175
notionalVolume: 30_000,
176-
marginDeployed: 2000,
177176
qualified: true,
178177
neighbors: [],
179178
computedAt: '2025-01-01T00:00:00.000Z',
@@ -288,9 +287,6 @@ describe('PerpsTradingCampaignStatsView', () => {
288287
expect(
289288
getByTestId(PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_VOLUME),
290289
).toBeDefined();
291-
expect(
292-
getByTestId(PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_MARGIN),
293-
).toBeDefined();
294290
});
295291

296292
it('shows last-computed when position has a timestamp', () => {
@@ -327,7 +323,7 @@ describe('PerpsTradingCampaignStatsView', () => {
327323
).toBeNull();
328324
});
329325

330-
it('hides volume and margin StatCells when campaign is complete (only PnL remains)', () => {
326+
it('hides volume StatCell when campaign is complete (only PnL remains)', () => {
331327
const completeCampaign = {
332328
...mockCampaign,
333329
endDate: '2020-01-01T00:00:00Z',
@@ -346,9 +342,6 @@ describe('PerpsTradingCampaignStatsView', () => {
346342
expect(
347343
queryByTestId(PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_VOLUME),
348344
).toBeNull();
349-
expect(
350-
queryByTestId(PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_MARGIN),
351-
).toBeNull();
352345
});
353346

354347
it('hides qualification cards when campaign is complete and shows last-computed after performance when position exists', () => {
@@ -384,7 +377,6 @@ describe('PerpsTradingCampaignStatsView', () => {
384377
...basePosition,
385378
qualified: false,
386379
notionalVolume: 5_000,
387-
marginDeployed: 500,
388380
},
389381
isLoading: false,
390382
hasError: false,

app/components/UI/Rewards/Views/PerpsTradingCampaignStatsView.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export const PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS = {
4747
CONTAINER: 'perps-campaign-stats-view-container',
4848
PERFORMANCE_PNL: 'perps-campaign-stats-view-performance-pnl',
4949
PERFORMANCE_VOLUME: 'perps-campaign-stats-view-performance-volume',
50-
PERFORMANCE_MARGIN: 'perps-campaign-stats-view-performance-margin',
5150
QUALIFIED_CARD: 'perps-campaign-stats-view-qualified-card',
5251
QUALIFY_FOR_RANK_CARD: 'perps-campaign-stats-view-qualify-for-rank-card',
5352
LAST_COMPUTED: 'perps-campaign-stats-view-last-computed',
@@ -96,7 +95,6 @@ const PerpsTradingCampaignStatsView: React.FC = () => {
9695
: TextColor.TextDefault;
9796

9897
const volumeValue = position ? formatUsd(position.notionalVolume) : '—';
99-
const marginValue = position ? formatUsd(position.marginDeployed) : '—';
10098
const isQualified = position != null && position.qualified;
10199
const isPending = position != null && !position.qualified;
102100

@@ -176,27 +174,18 @@ const PerpsTradingCampaignStatsView: React.FC = () => {
176174
valueColor={pnlColor}
177175
testID={PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_PNL}
178176
/>
179-
<Box twClassName="flex-1" />
180-
</Box>
181-
182-
{!isCampaignComplete && (
183-
<Box flexDirection={BoxFlexDirection.Row}>
177+
{!isCampaignComplete ? (
184178
<StatCell
185179
label={strings('rewards.perps_trading_campaign.label_volume')}
186180
value={volumeValue}
187181
isLoading={isLoading}
188182
suffix={isQualified ? <CheckIcon /> : undefined}
189183
testID={PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_VOLUME}
190184
/>
191-
<StatCell
192-
label={strings('rewards.perps_trading_campaign.label_margin')}
193-
value={marginValue}
194-
isLoading={isLoading}
195-
suffix={isQualified ? <CheckIcon /> : undefined}
196-
testID={PERPS_CAMPAIGN_STATS_VIEW_TEST_IDS.PERFORMANCE_MARGIN}
197-
/>
198-
</Box>
199-
)}
185+
) : (
186+
<Box twClassName="flex-1" />
187+
)}
188+
</Box>
200189

201190
{showQualifiedCard && (
202191
<Box

app/components/UI/Rewards/Views/PerpsTradingCampaignWinningView.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ describe('PerpsTradingCampaignWinningView', () => {
7070
rank: 3,
7171
pnl: 1500.25,
7272
notionalVolume: 30000,
73-
marginDeployed: 1200,
7473
qualified: true,
7574
neighbors: [],
7675
computedAt: '2025-08-15T12:00:00.000Z',

app/components/UI/Rewards/components/Campaigns/PerpsCampaignStatsSummary.test.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,13 @@ const basePosition: PerpsTradingCampaignLeaderboardPositionDto = {
6464
rank: 7,
6565
pnl: 1500.25,
6666
notionalVolume: 30_000,
67-
marginDeployed: 2000,
6867
qualified: true,
6968
neighbors: [],
7069
computedAt: '2025-01-01T00:00:00.000Z',
7170
};
7271

7372
describe('PerpsCampaignStatsSummary', () => {
74-
it('renders container and four stat labels', () => {
73+
it('renders container and three stat labels', () => {
7574
const { getByTestId, getByText } = render(
7675
<PerpsCampaignStatsSummary
7776
leaderboardPosition={basePosition}
@@ -87,9 +86,6 @@ describe('PerpsCampaignStatsSummary', () => {
8786
expect(
8887
getByText('rewards.perps_trading_campaign.label_volume'),
8988
).toBeDefined();
90-
expect(
91-
getByText('rewards.perps_trading_campaign.label_margin'),
92-
).toBeDefined();
9389
expect(getByText('07')).toBeDefined();
9490
expect(getByText('+$1,500.25')).toBeDefined();
9591
});
@@ -123,7 +119,7 @@ describe('PerpsCampaignStatsSummary', () => {
123119
leaderboard={null}
124120
/>,
125121
);
126-
expect(getAllByText('—').length).toBeGreaterThanOrEqual(4);
122+
expect(getAllByText('—').length).toBeGreaterThanOrEqual(3);
127123
});
128124

129125
it('shows pending tag on rank when campaign is active and user is not qualified', () => {
@@ -186,7 +182,7 @@ describe('PerpsCampaignStatsSummary', () => {
186182
expect(queryByTestId(TEST_IDS.QUALIFY_FOR_RANK_CARD)).toBeNull();
187183
});
188184

189-
it('hides volume and margin StatCells when campaign is complete (only rank and PnL remain)', () => {
185+
it('hides volume StatCell when campaign is complete (only rank and PnL remain)', () => {
190186
const { queryByTestId, getByTestId } = render(
191187
<PerpsCampaignStatsSummary
192188
leaderboardPosition={basePosition}
@@ -197,7 +193,6 @@ describe('PerpsCampaignStatsSummary', () => {
197193
expect(getByTestId(TEST_IDS.RANK)).toBeDefined();
198194
expect(getByTestId(TEST_IDS.PNL)).toBeDefined();
199195
expect(queryByTestId(TEST_IDS.NOTIONAL_VOLUME)).toBeNull();
200-
expect(queryByTestId(TEST_IDS.MARGIN_DEPLOYED)).toBeNull();
201196
});
202197

203198
it("hides You're qualified card when campaign is complete", () => {
@@ -219,7 +214,6 @@ describe('PerpsCampaignStatsSummary', () => {
219214
...basePosition,
220215
qualified: false,
221216
notionalVolume: 5_000,
222-
marginDeployed: 200,
223217
}}
224218
leaderboard={mockLeaderboard}
225219
/>,
@@ -236,7 +230,6 @@ describe('PerpsCampaignStatsSummary', () => {
236230
...basePosition,
237231
qualified: false,
238232
notionalVolume: 30_000,
239-
marginDeployed: 2_000,
240233
}}
241234
leaderboard={mockLeaderboard}
242235
/>,

app/components/UI/Rewards/components/Campaigns/PerpsCampaignStatsSummary.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export const PERPS_CAMPAIGN_STATS_SUMMARY_TEST_IDS = {
3232
RANK: 'perps-campaign-stats-summary-rank',
3333
PNL: 'perps-campaign-stats-summary-pnl',
3434
NOTIONAL_VOLUME: 'perps-campaign-stats-summary-notional-volume',
35-
MARGIN_DEPLOYED: 'perps-campaign-stats-summary-margin-deployed',
3635
PENDING_TAG: 'perps-campaign-stats-summary-pending-tag',
3736
QUALIFIED_TAG: 'perps-campaign-stats-summary-qualified-tag',
3837
QUALIFIED_CARD: 'perps-campaign-stats-summary-qualified-card',
@@ -81,10 +80,6 @@ const PerpsCampaignStatsSummary: React.FC<PerpsCampaignStatsSummaryProps> = ({
8180
? formatUsd(leaderboardPosition.notionalVolume)
8281
: '—';
8382

84-
const marginDisplay = leaderboardPosition
85-
? formatUsd(leaderboardPosition.marginDeployed)
86-
: '—';
87-
8883
const notionalGap = leaderboardPosition
8984
? Math.max(
9085
0,
@@ -140,11 +135,7 @@ const PerpsCampaignStatsSummary: React.FC<PerpsCampaignStatsSummaryProps> = ({
140135
value={volumeDisplay}
141136
testID={PERPS_CAMPAIGN_STATS_SUMMARY_TEST_IDS.NOTIONAL_VOLUME}
142137
/>
143-
<StatCell
144-
label={strings('rewards.perps_trading_campaign.label_margin')}
145-
value={marginDisplay}
146-
testID={PERPS_CAMPAIGN_STATS_SUMMARY_TEST_IDS.MARGIN_DEPLOYED}
147-
/>
138+
<Box twClassName="flex-1" />
148139
</Box>
149140
)}
150141

app/components/UI/Rewards/components/Campaigns/PerpsTradingCampaignStatsHeader.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ const basePosition: PerpsTradingCampaignLeaderboardPositionDto = {
5353
rank: 7,
5454
pnl: 1500.25,
5555
notionalVolume: 30_000,
56-
marginDeployed: 2000,
5756
qualified: true,
5857
neighbors: [],
5958
computedAt: '2025-01-01T00:00:00.000Z',

app/components/UI/Rewards/hooks/useGetPerpsTradingCampaignLeaderboardPosition.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ const MOCK_POSITION: PerpsTradingCampaignLeaderboardPositionDto = {
6464
rank: 5,
6565
pnl: 1500,
6666
notionalVolume: 30000,
67-
marginDeployed: 2000,
6867
qualified: true,
6968
neighbors: [],
7069
computedAt: '2024-03-20T12:00:00.000Z',

app/core/Engine/controllers/rewards-controller/RewardsController.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4603,7 +4603,6 @@ export class RewardsController extends BaseController<
46034603
rank: cached.rank,
46044604
pnl: cached.pnl,
46054605
notionalVolume: cached.notionalVolume,
4606-
marginDeployed: cached.marginDeployed,
46074606
qualified: cached.qualified,
46084607
neighbors: cached.neighbors,
46094608
computedAt: cached.computedAt,
@@ -4636,7 +4635,6 @@ export class RewardsController extends BaseController<
46364635
rank: payload.rank,
46374636
pnl: payload.pnl,
46384637
notionalVolume: payload.notionalVolume,
4639-
marginDeployed: payload.marginDeployed,
46404638
qualified: payload.qualified,
46414639
neighbors: payload.neighbors,
46424640
computedAt: payload.computedAt,

0 commit comments

Comments
 (0)