Skip to content

Feat/money account subscription controller - #10017

Open
tuna1207 wants to merge 6 commits into
mainfrom
feat/money-account-subscription-controller
Open

Feat/money account subscription controller#10017
tuna1207 wants to merge 6 commits into
mainfrom
feat/money-account-subscription-controller

Conversation

@tuna1207

@tuna1207 tuna1207 commented Aug 31, 2026

Copy link
Copy Markdown
Member

Explanation

  • Add a read-only MoneyAccountSubscriptionController that hydrates Money Account Plus plan and entitlements from the Profile JWT.
  • Add selectors for subscriber status, entitlement access, and usage availability.
  • Refresh JWT entitlements on subscription signals and every 60 seconds while an entitled user is on a trading surface.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
New subscription gating and JWT refresh logic (including performSignOut during force refresh) can affect fee waivers and feature access if claims are wrong or races mishandled; extensive tests mitigate but consumers must wire the controller correctly.

Overview
Introduces @metamask/money-account-subscription-controller, a read-only controller that derives Money Account Plus plan, entitlement flags, and subscriber status from the Profile JWT (productEntitlements.moneyAccountPlus), with JWT helpers and selectors for subscriber/entitlement/usage gating.

MoneyAccountSubscriptionController hydrates on sign-in and bootstrap (persisted state is cleared until re-validated), clears on sign-out, and reacts to SubscriptionController:stateChanged for money_account_plus lifecycle changes. forceRefresh coordinates sign-out/sign-in via AuthenticationController:performSignOut and a fresh bearer token, with throttling and queueing so stale async results do not overwrite newer auth. While a trading surface is active (startEntitlementRefresh / stopEntitlementRefresh), entitled signed-in users get 60s polling that triggers the same forced JWT refresh path.

Monorepo updates register the package under Web3Auth / team-shield in codeowners.ts, CODEOWNERS, teams.json, root tsconfig references, README package list and dependency graph, and yarn.lock.

Reviewed by Cursor Bugbot for commit 4691a6f. Bugbot is set up for automated code reviews on this repo. Configure here.

@tuna1207
tuna1207 marked this pull request as ready for review August 31, 2026 11:14
@tuna1207
tuna1207 requested a review from a team as a code owner August 31, 2026 11:14
@tuna1207
tuna1207 deployed to default-branch August 31, 2026 11:14 — with GitHub Actions Active

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4691a6f. Configure here.

}

this.clearState();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Force refresh signs the user out

High Severity

#forceBearerTokenRefresh calls AuthenticationController:performSignOut to rotate the JWT, including on the 60s poll. That clears global auth session data and publishes a real sign-out. This controller then always clearState(), which drops entitlements and stops polling via #syncPollingState. If getBearerToken fails, the user stays signed out, selectors stay fail-closed, and the refresh loop does not restart.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4691a6f. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this only clear the access token from the authentication controller, next time the access token is requested, a new access token will be fetched.

}

this.#moneyAccountSubscriptionSnapshot = nextSnapshot;
await this.forceRefresh();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Throttled subscription refresh is dropped

Medium Severity

#handleSubscriptionStateChange writes #moneyAccountSubscriptionSnapshot before forceRefresh(). If that refresh is throttled or coalesced onto an in-flight run, the newer snapshot is recorded without a later refresh. The next identical event is treated as unchanged, so a subscribe, cancel, or status change can leave JWT entitlements stale until another distinct snapshot arrives.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4691a6f. Configure here.

@cursor
cursor Bot requested review from bergarces and cryptodev-2s August 31, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant