Skip to content

feat: shield controller wire in wallet package - #9616

Open
tuna1207 wants to merge 17 commits into
mainfrom
feat/shield-controller-mm-wallet
Open

feat: shield controller wire in wallet package#9616
tuna1207 wants to merge 17 commits into
mainfrom
feat/shield-controller-mm-wallet

Conversation

@tuna1207

@tuna1207 tuna1207 commented Jul 23, 2026

Copy link
Copy Markdown
Member

Explanation

Adds ShieldController as a default initialized unit in @metamask/wallet.

@metamask/wallet is the shared controller-integration layer for extension, mobile, and other clients. Build-specific and runtime values are injected via instanceOptions rather than hardcoded.

Breaking changes

  • BREAKING: instanceOptions.shieldController is now required with at least baseUrl and fetchFunction.
  • When passing a full backend override, baseUrl and fetchFunction remain required at the type level but are unused for backend construction.
  • Hosts must register AuthenticationController and SignatureController on the wallet root messenger.
  • Hosts must explicitly call ShieldController:start after wiring - wallet initialization constructs the controller but does not auto-start it.

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

High Risk
Breaking wallet API plus shield-controller backend options; hosts must supply shield options, register Authentication/Signature controllers, and start Shield explicitly—misconfiguration can break transaction/signature protection flows.

Overview
ShieldController is now a default @metamask/wallet instance, with a required instanceOptions.shieldController slot (fetchFunction plus optional env, backend override, and tuning). Initialization builds a default remote backend via createShieldRemoteBackend, delegates AuthenticationController:getBearerToken and transaction/signature :stateChange events, and does not auto-call ShieldController:start.

In @metamask/shield-controller, ShieldRemoteBackend / the factory take env (DEV / UAT / PRD) instead of baseUrl; URLs come from getShieldApiBaseUrl. The factory defaults auth to AuthenticationController:getBearerToken on the shield messenger (new allowed action). profile-sync-controller is added for bearer-token types.

wallet-cli passes fetchFunction only (production env default). Tests and CODEOWNERS/dependency graphs are updated for the new wiring.

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

@tuna1207
tuna1207 marked this pull request as ready for review July 23, 2026 00:58
@tuna1207
tuna1207 requested review from a team as code owners July 23, 2026 00:58
@tuna1207
tuna1207 temporarily deployed to default-branch July 23, 2026 00:58 — with GitHub Actions Inactive

@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 1 potential issue.

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 0fc9607. Configure here.

Comment thread packages/wallet-cli/src/daemon/wallet-factory.ts
@tuna1207
tuna1207 requested a review from a team as a code owner July 23, 2026 01:29
Comment thread packages/wallet/src/initialization/instances/shield-controller/types.ts Outdated
Comment thread packages/wallet/src/initialization/instances/shield-controller/types.ts Outdated
Comment thread packages/wallet/src/initialization/instances/shield-controller/types.ts Outdated
transactionController?: TransactionControllerInstanceOptions;
passkeyController?: PasskeyControllerInstanceOptions;
seedlessOnboardingController?: SeedlessOnboardingControllerInstanceOptions;
shieldController: ShieldControllerInstanceOptions;

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.

Why shieldController is required type here?

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.

Most controllers/services are optional 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.

shieldController is required because it follows the same rule as the other required slots.
Needs baseUrl + fetchFunction, or an injected backend.

initialize() always creates ShieldController (it's in defaultConfigurations). Making the slot required forces every Wallet consumer to explicitly provide Shield connectivity config, similar to gasFeeController.clientId.

@pedronfigueiredo pedronfigueiredo left a comment

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.

I see there's a bunch of comments and merge conflicts, can you address them and then re-request my review here on github once it's ready for another look?

storage: new InMemoryStorageAdapter(),
},
remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS,
shieldController: {

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.

this should no longer be needed

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.

shieldController option is still required because of required fetchFunction: globalThis.fetch, do you think we should default this option ?

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.

Not quite true: fetchFunction is not required when a backend is supplied. The type is a union: either provide backend, or provide fetchFunction so a backend can be created. The test currently provides both, but fetchFunction is ignored because initialization uses:

  options.backend ?? createShieldRemoteBackend(...)

So this would be sufficient:

  shieldController: {
    backend: {
      checkCoverage: jest.fn(),
      checkSignatureCoverage: jest.fn(),
      logSignature: jest.fn(),
      logTransaction: jest.fn(),
    },
  },

@mcmire mcmire left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, just had one immediate comment and one future comment.

env = Env.PRD,
...options
}: CreateShieldRemoteBackendOptions): ShieldRemoteBackend {
return new ShieldRemoteBackend({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm, why does this package need to export a convenience function to export this class? If we are initializing this class in @metamask/wallet then do we need this?

state,
backend:
options.backend ??
createShieldRemoteBackend({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar to the comment I posted about SubscriptionController, it would be better if ShieldRemoteBackend were a data service class, so that it could access AuthenticationController:getBearerToken itself through the messenger (see ChompApiService for an example.

Also it looks like ShieldController takes ShieldRemoteBackend as an argument. If the service had its own messenger, then it would be better if the controller could use it to access the service itself (see ClaimsController and ClaimsService.

Again this could be a future change, but I wanted to point this out.

},
remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS,
},
instanceOptions: getInstanceOptions(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Instead of extracting getInstanceOptions, what do you think about having setupWallet take an option that allows for overriding options? Then the tests that use new Wallet could use setupWallet instead, and pass whatever is relevant to them (e.g. initializationConfigurations)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants