feat(rewards): campaign opt-in and participant status#27121
Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
| if (!isCampaignsEnabled || !subscriptionId || !campaignId) { | ||
| setStatus(null); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Stale error state persists when guard conditions trigger
Low Severity
The guard path in fetchStatus sets setStatus(null) but does not reset hasError or isLoading. If a previous fetch failed (setting hasError to true), and then the feature flag is toggled off or subscriptionId / campaignId become unavailable, the hook returns { status: null, hasError: true }. A consumer relying on hasError would display an error state even though the real reason for null status is that the prerequisites aren't met, not a fetch failure. The happy path correctly resets hasError via setHasError(false) in the try block, but the guard path skips this reset entirely.
Expose the new GET /campaigns backend endpoint via the mobile rewards stack: - Add CampaignDto, CampaignType enum, and CampaignsState types - Add RewardsControllerGetCampaignsAction to controller actions - Implement getCampaigns in RewardsDataService with auth support - Implement getCampaigns in RewardsController with 5-minute cache and #withAuthRetry for 403 recovery - Add campaigns to controller state metadata and default state - Add unit tests for both data service and controller - Update initial-background-state.json and inline state snapshots Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rdCampaigns hook
- Add CAMPAIGNS_REWARDS_FLAG_NAME ('rewardsCampaignsEnabled') selector with
raw and gated variants in rewardsEnabled.ts; export from rewards flag index
- Inject isCampaignsEnabled into RewardsController constructor (defaults false);
getCampaigns now guards on both isRewardsFeatureEnabled() and isCampaignsEnabled()
- Wire selectCampaignsRewardsEnabledFlag into rewardsControllerInit
- Add campaigns slice state (campaigns, campaignsLoading, campaignsError) to
rewards reducer with setCampaigns / setCampaignsLoading / setCampaignsError actions
- Add selectCampaigns / selectCampaignsLoading / selectCampaignsError selectors
- Implement useRewardCampaigns hook: fetches via RewardsController:getCampaigns,
returns [] when flag is off or subscriptionId is absent, similar to useSnapshots
- Add 12 useRewardCampaigns tests covering flag guard, success, error, focus
effect and invalidation event wiring
- Add 2 new RewardsController getCampaigns tests for the campaigns feature flag;
update existing tests to pass isCampaignsEnabled: () => true
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…all sites Inline array literals passed to useInvalidateByRewardEvents created a new reference on every render, causing the useEffect inside the hook to unsubscribe and re-subscribe to all events every cycle. Wrap each array in useMemo with an empty dep array so the reference is stable across renders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adds `CampaignParticipantStatusDto` and `CampaignParticipantStatusState` types - Adds `RewardsDataService.optInToCampaign` (POST /campaigns/:id/opt-in) and `getCampaignParticipantStatus` (GET /campaigns/:campaignId/status) - Adds `RewardsController.optInToCampaign` (feature-flag gated, no cache) and `getCampaignParticipantStatus` (5-min cache via wrapWithCache) - Wires new actions through rewards-controller-messenger - Adds `useOptInToCampaign` hook and `useGetCampaignParticipantStatus` hook, both short-circuit when `rewardsCampaignsEnabled` flag is off - Updates state snapshots and initial-background-state for new `campaignParticipantStatus` state field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
19b3f4f to
237ee93
Compare
Fixes TS2300 duplicate identifier error in rewards-data-service.test.ts that was causing scripts(lint:tsc) CI check to fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ingDecision in fixture validation - Remove duplicate "campaigns" entry in RewardsController.test.ts inline snapshot (TS2300/snapshot pattern: same key added twice during merge) - Add fiatOrders.rampRoutingDecision to getMobileFixtureIgnoredKeys() as it is non-deterministic (null vs string depending on app state/geo), similar to the already-ignored fiatOrders.detectedGeolocation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: There are no modifications to Engine.ts initialization order, no changes to foundational controllers (Network, Keyring, Accounts, Transactions), and no evidence of cross-controller dependency changes beyond the rewards-specific messenger. Therefore, cascade risk to transactions, accounts, swaps, network selection, or multi-chain flows appears low. However, because this is a core Engine controller with a messenger configuration change and potential Redux background state integration, there is moderate integration risk (e.g., state sync, messenger wiring, or background state shape). To validate that core wallet flows remain stable and that Engine state synchronization is not inadvertently affected, running SmokeWalletPlatform provides coverage of:
No other tags (Accounts, Confirmations, Trade, Network, Snaps, etc.) are directly implicated by the Rewards domain changes. The change in tests/framework/fixtures/fixture-validation.ts is test-infrastructure-related but does not itself require expanding tag scope beyond a core platform sanity check. No explicit dependencies require adding companion tags (e.g., SmokeConfirmations with Trade, etc.). Performance Test Selection: |
|
✅ E2E Fixture Validation — Schema is up to date |
|





Summary
Mobile counterpart to
va-mmcx-rewards#470, built on top of#27108.CampaignParticipantStatusDto({ optedIn: boolean }) andCampaignParticipantStatusStatetotypes.ts; adds new state fieldcampaignParticipantStatustoRewardsControllerStateoptInToCampaign(subscriptionId, campaignId)→POST /campaigns/:id/opt-in;getCampaignParticipantStatus(subscriptionId, campaignId)→GET /campaigns/:campaignId/statusoptInToCampaign(no cache, feature-flag gated);getCampaignParticipantStatus(5-min cache viawrapWithCache, feature-flag gated)rewards-controller-messengeruseOptInToCampaign— mutation hook, returnsnullwhen flag off or no subscriptionuseGetCampaignParticipantStatus(campaignId)— fetch-on-mount hook, returnsnullstatus when flag offBoth hooks short-circuit on the
rewardsCampaignsEnabledremote feature flag.Test plan
useOptInToCampaign.test.ts— 5 tests (flag disabled, missing sub, success, error, clear error)useGetCampaignParticipantStatus.test.ts— 4 tests (flag disabled, success, error, refetch)RewardsController.test.ts— 3 state-metadata snapshots updated (679 tests pass)util/logs/index.test.ts— state-log snapshot updatedCHANGELOG entry: feat(rewards): add campaign opt-in and participant status hooks/controller actions
🤖 Generated with Claude Code
Note
Medium Risk
Adds new rewards API surface area (opt-in + status) with persisted controller state and caching/event invalidation, which could affect data freshness and state migrations. Risk is mitigated by feature-flag gating and comprehensive unit tests/snapshots.
Overview
Adds campaign opt-in + participant status support under the campaigns rewards feature flag. This introduces
RewardsController:optInToCampaignandRewardsController:getCampaignParticipantStatus, backed by newRewardsDataServiceendpoints (POST /wr/campaigns/:id/opt-in,GET /campaigns/:id/status).Adds cached participant-status state and invalidation.
RewardsControllernow persists acampaignParticipantStatuscache (5-minute TTL) and publishes a newRewardsController:campaignOptedInevent on successful opt-in to invalidate cached status and trigger refetch.Exposes new UI hooks with tests. Adds
useOptInToCampaign(mutation w/ loading + error state) anduseGetCampaignParticipantStatus(fetch-on-mount + manual refetch + event-driven refetch), plus updates snapshots/fixtures and removes a previously-added concurrent-fetch test inuseRewardCampaigns.test.ts.Written by Cursor Bugbot for commit ea0c01f. This will update automatically on new commits. Configure here.