Skip to content

feat(rewards): expose GET /campaigns endpoint through RewardsController#27108

Merged
sophieqgu merged 12 commits into
mainfrom
feat/rwds-campaigns-endpoint
Mar 6, 2026
Merged

feat(rewards): expose GET /campaigns endpoint through RewardsController#27108
sophieqgu merged 12 commits into
mainfrom
feat/rwds-campaigns-endpoint

Conversation

@VGR-GIT
Copy link
Copy Markdown
Contributor

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

Summary

  • Adds CampaignType enum and CampaignDto / CampaignsState types matching the backend DTO from va-mmcx-rewards#469
  • Implements getCampaigns(subscriptionId) in RewardsDataService calling GET /campaigns with subscription auth
  • Implements getCampaigns(subscriptionId) in RewardsController with 5-minute cache via wrapWithCache and #withAuthRetry for 403 recovery
  • Adds campaigns to controller state metadata/default state (persisted, usedInUi)
  • Registers RewardsDataService:getCampaigns action in the rewards-controller messenger
  • Updates initial-background-state.json and state-logs snapshot

Backend model (from va-mmcx-rewards#469)

CampaignDto {
  id: string
  type: CampaignType  // 'ONDO_HOLDING'
  name: string
  startDate: string
  endDate: string
  termsAndConditions: Json | null
  excludedRegions: string[]
  statusLabel: string
}

Test plan

  • rewards-data-service.test.ts — 3 new getCampaigns tests: success (correct endpoint + auth headers), empty array, error response
  • RewardsController.test.ts — 5 new getCampaigns tests: disabled flag, API fetch + cache write, cache hit (fresh), cache miss (stale), logging
  • All 727 rewards controller tests passing
  • Prettier and ESLint clean (no errors)

CHANGELOG entry: feat(rewards): expose GET /campaigns endpoint through RewardsController with 5-minute cache

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new rewards API surface (getCampaigns) with caching and persisted controller/UI state, plus a new feature flag controlling behavior. Moderate risk due to new network path and state persistence/invalidation changes affecting rewards UI refresh timing.

Overview
Adds rewards campaigns support end-to-end. Introduces CampaignType/CampaignDto types, a new RewardsDataService:getCampaigns action that calls GET /campaigns, and a RewardsController:getCampaigns handler that caches results for 5 minutes and persists them in controller state.

Wires campaigns into the UI/store and tightens event invalidation. Adds campaigns fields/actions/selectors to the rewards reducer, a useRewardCampaigns hook (focus fetch + invalidation on account/balance events, gated by selectCampaignsRewardsEnabledFlag), and updates useInvalidateByRewardEvents + several rewards hooks to memoize their event lists and accept readonly event arrays to avoid unnecessary re-subscriptions. Tests and state snapshots/initial background state are updated accordingly.

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

VGR-GIT and others added 6 commits March 6, 2026 09:54
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>
- Remove duplicate RewardsDataServiceGetSnapshotsAction from union
- Rename getCampaigns data service test names to drop 'should' prefix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update index.test.ts.snap to include the new campaigns field in
the serialized RewardsController state output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add RewardsDataServiceGetCampaignsAction to AllowedActions and delegate
the RewardsDataService:getCampaigns action through the messenger so the
RewardsController can call it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change CampaignDto.termsAndConditions from Record<string, unknown> to
  Json | null to satisfy StateConstraint (state must be JSON-serializable)
- Inline CampaignsState campaigns array type (same pattern as SnapshotsState)
  so TypeScript can verify StateConstraint compliance
- Update createTestCampaign helper to use CampaignType enum and Json type

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 6, 2026 09:00
@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-M label Mar 6, 2026
Comment thread app/core/Engine/controllers/rewards-controller/types.ts
@metamaskbot metamaskbot added the INVALID-PR-TEMPLATE PR's body doesn't match template label Mar 6, 2026
…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>
@github-actions github-actions Bot added size-L and removed size-M labels Mar 6, 2026
…tests

Two explicit RewardsState object literals in index.test.ts were missing
the new campaigns/campaignsLoading/campaignsError fields added to the
reducer, causing tsc to fail with TS2739.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread app/components/UI/Rewards/hooks/useRewardCampaigns.ts
…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>
@github-actions github-actions Bot added size-XL and removed size-L labels Mar 6, 2026
TypeScript infers tuple literals as readonly arrays, so callers passing
['RewardsController:accountLinked', ...] were failing strict type checks.
Widening the parameter to readonly RewardEvent[] fixes all affected hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread app/components/UI/Rewards/hooks/useRewardCampaigns.test.ts
Add tests for setCampaigns/setCampaignsLoading/setCampaignsError reducers,
selectCampaigns/selectCampaignsLoading/selectCampaignsError state selectors,
selectCampaignsRewardsEnabledRawFlag/selectCampaignsRewardsEnabledFlag feature
flag selectors, and concurrent fetch guard in useRewardCampaigns hook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread app/reducers/rewards/index.test.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 6, 2026

🔍 Smart E2E Test Selection

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

E2E Test Selection:
This PR adds a new "Campaigns" feature to the existing Rewards system. The changes are:

  1. Additive Controller Changes: New getCampaigns() method added to RewardsController with caching, new state property campaigns, and new action handler. No existing functionality is modified.

  2. Feature Flag Gated: The new campaigns feature is controlled by rewardsCampaignsEnabled feature flag, meaning it won't affect users unless explicitly enabled.

  3. No Existing E2E Tests: There are no E2E tests for the Rewards feature in the test suite (searched for rewards in *.feature and *.spec.js files - no matches found).

  4. Isolated Feature: The Rewards system is a self-contained feature that doesn't directly impact core wallet functionality like transactions, accounts, networks, confirmations, or trading flows.

  5. Minor Refactors: The changes to existing hooks (useActivePointsBoosts, usePointsEvents, useReferralDetails, useRewardOptinSummary, useSnapshots, useUnlockedRewards) are purely type safety improvements - wrapping event arrays in useMemo with as const to satisfy the updated readonly type requirement in useInvalidateByRewardEvents.

  6. Comprehensive Unit Tests: The PR includes extensive unit tests for the new functionality (RewardsController.test.ts, rewardsEnabled.test.ts, useRewardCampaigns.test.ts, rewards-data-service.test.ts, selectors.test.ts).

Since there are no E2E tests for Rewards functionality and the changes are additive, feature-flag gated, and don't affect any core wallet flows covered by the available E2E test tags, no E2E tests need to be run.

Performance Test Selection:
The changes are to the Rewards feature which is a specialized feature that doesn't impact core app performance metrics. The new campaigns functionality is feature-flag gated and involves API calls with caching (5-minute TTL), which is standard async data fetching. The changes don't affect: app launch/startup, login flow, account list rendering, swap flows, asset loading, or any other performance-critical paths. The minor refactors to existing hooks (adding useMemo for event arrays) are micro-optimizations that won't have measurable performance impact. No performance tests are needed.

View GitHub Actions results

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.

}

return (await response.json()) as CampaignDto[];
}
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.

Missing 403/401 AuthorizationFailedError in getCampaigns service

Medium Severity

The getCampaigns method checks !response.ok and throws a generic Error, but for a 401 response the makeRequest helper only auto-throws AuthorizationFailedError for 403. If the backend returns 401 (as it can for expired tokens), #withAuthRetry in the controller won't catch it for re-authentication, unlike getSubscriptionAccounts which explicitly handles 401 with AuthorizationFailedError.

Fix in Cursor Fix in Web

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 6, 2026

@VGR-GIT VGR-GIT requested a review from sophieqgu March 6, 2026 12:18
@VGR-GIT VGR-GIT enabled auto-merge March 6, 2026 13:38
@VGR-GIT VGR-GIT added this pull request to the merge queue Mar 6, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Mar 6, 2026
@sophieqgu sophieqgu added this pull request to the merge queue Mar 6, 2026
Merged via the queue into main with commit 164dbcb Mar 6, 2026
61 of 62 checks passed
@sophieqgu sophieqgu deleted the feat/rwds-campaigns-endpoint branch March 6, 2026 17:33
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 6, 2026
@metamaskbot metamaskbot added the release-7.70.0 Issue or pull request that will be included in release 7.70.0 label Mar 6, 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.

3 participants