Commit 08675af
feat(rewards): add Perps Trading campaign participant outcome (#29648)
## **Description**
Adds Perps Trading campaign outcome support to the Rewards stack,
mirroring the Ondo GM outcome pattern.
After a Perps Trading campaign ends, opted-in participants can query
`GET /perps-trading/:campaignId/outcome/me` to learn whether they won.
This PR wires that endpoint into the mobile app end-to-end:
- **Winners** (have a `winnerVerificationCode` + `pending` status) see a
toast → tap → `PerpsTradingCampaignWinningView` showing their rank,
verification code (copy or email to claim prize)
- **Non-winners once results are final** (`finalized` status, no code)
see a toast → tap → campaigns view
## **Changelog**
CHANGELOG entry: null
## **Related issues**
Fixes: rwds-perps-trading-outcome
## **Changes**
| Layer | File | What |
|---|---|---|
| Types | `types.ts` | `CampaignType.PERPS_TRADING`,
`PerpsTradingCampaignParticipantOutcomeDto` |
| Data service | `rewards-data-service.ts` |
`getPerpsTradingCampaignParticipantOutcome()` → `GET
/perps-trading/:campaignId/outcome/me` |
| Controller | `RewardsController.ts` | Method with 10-min in-memory
cache + auth retry |
| Action types / Messenger | multiple | Registered in action union and
allowed actions |
| Hook | `usePerpsTradingCampaignParticipantOutcome.ts` | Fetches
outcome via controller messenger |
| Toast hook | `usePerpsTradingCampaignEndedOutcomeToast.ts` |
`winner_pending` → winning view; `participant_finalized` → campaigns |
| Screen | `PerpsTradingCampaignWinningView.tsx` | Rank + verification
code, copy + mailto |
| Utility | `formatUtils.ts` | `formatOrdinalRank()` (1st/2nd/3rd…) |
| Routes / Navigator | multiple | New route constant, screen registered,
toast hook wired |
| Strings | `en.json` | Toast + winning view copy |
## **Manual testing steps**
```gherkin
Feature: Perps Trading campaign outcome
Scenario: winner sees outcome toast and winning view
Given a Perps Trading campaign that has ended
And the current user is opted in and has a winner row with status "pending" and a verification code
When the user opens the Rewards section
Then a toast appears: "You won! 🏆" with "View details" link
When the user taps "View details"
Then the PerpsTradingCampaignWinningView opens showing their rank and verification code
And the user can copy the code or tap "Open mail" to email perpscampaign@consensys.net
Scenario: non-winner sees finalized toast
Given a Perps Trading campaign that has ended with 20 finalized winners
And the current user is opted in but has no winner row
When the user opens the Rewards section
Then a toast appears: "The results are in" with "View" link
When the user taps "View"
Then the user is navigated to the campaigns view
```
## **Screenshots/Recordings**
### **Before**
No Perps Trading outcome UI.
### **After**
<img width="1179" height="2556" alt="Simulator Screenshot - E2E Test -
2026-05-05 at 19 33 02"
src="https://github.com/user-attachments/assets/c2d21dda-6db6-4260-b0b3-480cbea38f18"
/>
<img width="1179" height="2556" alt="Simulator Screenshot - E2E Test -
2026-05-05 at 20 50 09"
src="https://github.com/user-attachments/assets/aae8fafb-2bac-4349-83d7-ba00182fa498"
/>
<img width="1179" height="2556" alt="Simulator Screenshot - E2E Test -
2026-05-05 at 20 53 05"
src="https://github.com/user-attachments/assets/425fc361-d51f-4d54-a8e6-39f3b34fe1c3"
/>
<img width="1179" height="2556" alt="Simulator Screenshot - E2E Test -
2026-05-05 at 20 53 10"
src="https://github.com/user-attachments/assets/c96b8fb0-c19d-4b85-a1a4-81af881aa612"
/>
_(to be added — requires a completed Perps Trading campaign with backend
data)_
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds new post-campaign outcome UI/UX (toasts, auto-navigation, new
routes/screens) that can affect user navigation and state gating across
Rewards; issues would mainly surface as incorrect redirects or missing
banners/toasts.
>
> **Overview**
> Adds a shared `CampaignWinningView` and wires both Ondo and Perps
campaigns to use it for winner verification-code display (copy + mailto)
with a consistent fallback when no code is available.
>
> Introduces a generic `useCampaignParticipantOutcome` +
`useCampaignOutcomeToast` pattern, then adds Perps Trading support via
`usePerpsTradingCampaignParticipantOutcome` and
`usePerpsTradingCampaignEndedOutcomeToast`, mounting these toasts on
`RewardsDashboard` and `CampaignsView`.
>
> Expands Perps Trading end-of-campaign UX: new
`PerpsTradingCampaignWinningView` route/screen, outcome banners on
details/stats (with one-time session auto-navigation for pending
winners), an ended-campaign stats panel, and tweaks to
completed-campaign stat presentation (hide pending tag; hide
volume/margin once complete). Also consolidates outcome banner locale
keys and generalizes `CampaignOutcomeBanner` usage across campaigns.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
464bd29. 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: sophieqgu <sophieqgu@gmail.com>1 parent 5011f84 commit 08675af
85 files changed
Lines changed: 4352 additions & 1940 deletions
File tree
- app
- components/UI/Rewards
- Views
- components
- Campaigns
- ReferralDetails
- hooks
- core/Engine
- controllers/rewards-controller
- services
- messengers/rewards-controller-messenger
- reducers/rewards
- selectors/rewards
- locales/languages
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
282 | 282 | | |
283 | 283 | | |
284 | 284 | | |
| 285 | + | |
| 286 | + | |
285 | 287 | | |
286 | 288 | | |
287 | 289 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
40 | 41 | | |
41 | 42 | | |
42 | | - | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
299 | 304 | | |
300 | 305 | | |
301 | 306 | | |
| |||
Lines changed: 297 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
0 commit comments