Skip to content

feat(rewards): campaign opt-in and participant status#27121

Merged
VGR-GIT merged 7 commits into
mainfrom
feat/rwds-campaign-opt-in
Mar 9, 2026
Merged

feat(rewards): campaign opt-in and participant status#27121
VGR-GIT merged 7 commits into
mainfrom
feat/rwds-campaign-opt-in

Conversation

@VGR-GIT
Copy link
Copy Markdown
Contributor

@VGR-GIT VGR-GIT commented Mar 6, 2026

Summary

Mobile counterpart to va-mmcx-rewards#470, built on top of #27108.

  • Types: adds CampaignParticipantStatusDto ({ optedIn: boolean }) and CampaignParticipantStatusState to types.ts; adds new state field campaignParticipantStatus to RewardsControllerState
  • Data service: optInToCampaign(subscriptionId, campaignId)POST /campaigns/:id/opt-in; getCampaignParticipantStatus(subscriptionId, campaignId)GET /campaigns/:campaignId/status
  • Controller: optInToCampaign (no cache, feature-flag gated); getCampaignParticipantStatus (5-min cache via wrapWithCache, feature-flag gated)
  • Messenger: both new service actions delegated through rewards-controller-messenger
  • Hooks:
    • useOptInToCampaign — mutation hook, returns null when flag off or no subscription
    • useGetCampaignParticipantStatus(campaignId) — fetch-on-mount hook, returns null status when flag off

Both hooks short-circuit on the rewardsCampaignsEnabled remote 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 updated
  • Prettier + ESLint clean

CHANGELOG 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:optInToCampaign and RewardsController:getCampaignParticipantStatus, backed by new RewardsDataService endpoints (POST /wr/campaigns/:id/opt-in, GET /campaigns/:id/status).

Adds cached participant-status state and invalidation. RewardsController now persists a campaignParticipantStatus cache (5-minute TTL) and publishes a new RewardsController:campaignOptedIn event on successful opt-in to invalidate cached status and trigger refetch.

Exposes new UI hooks with tests. Adds useOptInToCampaign (mutation w/ loading + error state) and useGetCampaignParticipantStatus (fetch-on-mount + manual refetch + event-driven refetch), plus updates snapshots/fixtures and removes a previously-added concurrent-fetch test in useRewardCampaigns.test.ts.

Written by Cursor Bugbot for commit ea0c01f. This will update automatically on new commits. Configure here.

@VGR-GIT VGR-GIT requested a review from a team as a code owner March 6, 2026 11:19
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 6, 2026

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.

@metamaskbot metamaskbot added the team-rewards Rewards team label Mar 6, 2026
@github-actions github-actions Bot added the size-L label Mar 6, 2026
Comment thread app/core/Engine/controllers/rewards-controller/RewardsController.ts
@metamaskbot metamaskbot added the INVALID-PR-TEMPLATE PR's body doesn't match template label Mar 6, 2026
@VGR-GIT VGR-GIT requested a review from sophieqgu March 6, 2026 12:18
if (!isCampaignsEnabled || !subscriptionId || !campaignId) {
setStatus(null);
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Base automatically changed from feat/rwds-campaigns-endpoint to main March 6, 2026 17:33
VGR-GIT and others added 4 commits March 9, 2026 09:06
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>
@VGR-GIT VGR-GIT force-pushed the feat/rwds-campaign-opt-in branch from 19b3f4f to 237ee93 Compare March 9, 2026 08:09
@github-actions github-actions Bot added size-XL and removed size-L labels Mar 9, 2026
VGR-GIT and others added 2 commits March 9, 2026 09:17
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>
@VGR-GIT VGR-GIT requested a review from a team as a code owner March 9, 2026 08:45
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread app/components/UI/Rewards/hooks/useRewardCampaigns.test.ts
EugeniyBykov
EugeniyBykov previously approved these changes Mar 9, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeWalletPlatform
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 72%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are concentrated in the RewardsController (app/core/Engine/controllers/rewards-controller/*), its associated messenger, types, and service layer, along with related UI hooks under app/components/UI/Rewards/hooks. This is a Phase 6 (business logic) controller integrated into the Engine, but it is not part of critical dependency chains like NetworkController, KeyringController, or TransactionController.

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:

  • Engine → Redux background state synchronization
  • Transaction history rendering
  • Multi-SRP architecture flows
  • Core wallet lifecycle and provider event handling

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:
The changes are limited to rewards business logic, controller services, types, and UI hooks. There are no modifications to high-impact rendering paths (account list, asset loading, swaps, onboarding, launch, predictions, perps) or to performance-critical controllers (Network, Accounts, Transactions). Therefore, no performance tests are required.

View GitHub Actions results

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

E2E Fixture Validation — Schema is up to date
9 value mismatches detected (expected — fixture represents an existing user).
View details

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 9, 2026

@VGR-GIT VGR-GIT enabled auto-merge March 9, 2026 14:31
@VGR-GIT VGR-GIT added this pull request to the merge queue Mar 9, 2026
Merged via the queue into main with commit b8a9a95 Mar 9, 2026
109 of 110 checks passed
@VGR-GIT VGR-GIT deleted the feat/rwds-campaign-opt-in branch March 9, 2026 14:59
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 9, 2026
@metamaskbot metamaskbot added the release-7.70.0 Issue or pull request that will be included in release 7.70.0 label Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

INVALID-PR-TEMPLATE PR's body doesn't match template release-7.70.0 Issue or pull request that will be included in release 7.70.0 size-XL team-rewards Rewards team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants