From b5217898cb0536003af80e79796589e0630d5dba Mon Sep 17 00:00:00 2001 From: grypez <143971198+grypez@users.noreply.github.com> Date: Mon, 18 May 2026 12:39:02 -0400 Subject: [PATCH] docs(wallet): record init-messenger pattern decision Documents the decision to defer the two-messenger pattern (controller messenger + init messenger) in favour of the simpler single-messenger approach. The per-controller init log lines added by WalletOptions.logger provide the instrumentation needed to revisit this empirically when the controller count grows. Also links the decision document from the README Architecture decisions section. Closes #8790 Co-Authored-By: Claude Sonnet 4.6 --- packages/wallet/README.md | 4 ++ packages/wallet/init-messenger-decision.md | 44 ++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 packages/wallet/init-messenger-decision.md diff --git a/packages/wallet/README.md b/packages/wallet/README.md index 390ea53c68..c2cfc5d65a 100644 --- a/packages/wallet/README.md +++ b/packages/wallet/README.md @@ -43,6 +43,10 @@ for (const [name, metadata] of Object.entries(wallet.controllerMetadata)) { The `patches` argument contains Immer patches identifying exactly which top-level fields changed, so writes can be scoped rather than full-state replacements. +## Architecture decisions + +- [Init-messenger pattern](./init-messenger-decision.md) — why the wallet uses a single messenger per controller rather than the extension/mobile two-messenger pattern, and when to revisit. + ## Contributing This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). diff --git a/packages/wallet/init-messenger-decision.md b/packages/wallet/init-messenger-decision.md new file mode 100644 index 0000000000..73099b0e15 --- /dev/null +++ b/packages/wallet/init-messenger-decision.md @@ -0,0 +1,44 @@ +# Init-messenger pattern decision + +## Background + +Both `metamask-extension` and `metamask-mobile` create **two** restricted +messengers per controller: + +- A **controller messenger** — handed to the controller constructor; defines + what the controller may do for the rest of its lifetime. +- An **init messenger** — used only inside the controller's `init()` function + during boot (broader privileges: one-shot capability queries, approval handler + registration, boot-time telemetry). Discarded once initialization completes. + +The split keeps runtime messenger allowlists narrow at the cost of additional +boilerplate per controller. + +`@metamask/wallet` currently uses a **single messenger per controller**. Some +controllers (notably `TransactionController`) keep init-time actions in their +runtime allowlist because there is no separate init messenger to put them on. + +## Decision: defer + +We are not adopting the two-messenger pattern at this time. The single-messenger +approach is functionally correct; the only cost is a slightly wider runtime +allowlist for a small number of controllers. That cost is acceptable while the +controller inventory is small. + +## Revisit trigger + +Each controller's `init()` call is bracketed by a `[wallet] ${name}: initialized` +log line (emitted when `WalletOptions.logger` is provided). This makes it +straightforward to instrument which messenger actions are invoked before vs. +after each init-complete boundary. + +Revisit this decision if: + +- A controller's runtime allowlist grows large enough to be a meaningful security + concern (i.e. it can call actions it has no legitimate runtime reason to call). +- Profiling shows init-time messenger calls are a material source of latency and + isolating them would help. +- The controller count grows to the point where the allowlist unions become + unwieldy for TypeScript to infer. + +Until one of those conditions is met, keep the single-messenger pattern.