fix(gas-fee-controller): make GasFeeController initialization-order-agnostic - #9569
Merged
Merged
Conversation
sirtimid
force-pushed
the
sirtimid/gas-fee-controller-init-order-agnostic
branch
from
July 21, 2026 10:40
6aedfa1 to
b0a9c28
Compare
sirtimid
marked this pull request as ready for review
July 21, 2026 10:40
5 tasks done
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit b0a9c28. Configure here.
…gnostic Defer the constructor's NetworkController and provider reads until the first gas fee fetch. The constructor no longer eagerly calls NetworkController:getState, NetworkController:getNetworkClientById, the getChainId option, or the getProvider option; the provider (EthQuery) and current chain ID are now resolved lazily on first use and memoized, kept fresh by the networkDidChange handler. This lets the controller be constructed before NetworkController is ready, removing the need for dependency ordering when wiring it into the shared @metamask/wallet init set. The constructor signature and gas fee fetching behavior are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… eth query rebuild Resolve the current chain ID into a local before entering the update() recipe so the Immer producer stays side-effect free, and extract #getChainIdForNetworkClient to dedupe the network-client chain-ID lookup shared by the network-change handler and the lazy resolver. Add a test asserting the provider is read once, the eth query is cached, then rebuilt from the provider after a network change (mutation-verified), and reuse setupGasFeeController for the messenger-event test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The order-agnostic refactor removed two no-restricted-syntax violations in GasFeeController.ts, leaving stale suppression counts that fail lint:eslint in CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sirtimid
force-pushed
the
sirtimid/gas-fee-controller-init-order-agnostic
branch
from
July 21, 2026 13:43
5d65742 to
66c249d
Compare
matthewwalsh0
approved these changes
Jul 21, 2026
sirtimid
added a commit
that referenced
this pull request
Jul 21, 2026
Wire `GasFeeController` into `@metamask/wallet`'s default initialization set as its own `InitializationConfiguration`, mirroring the `transaction-controller` / `network-controller` instances. A wired `TransactionController` delegates `GasFeeController:fetchGasFeeEstimates` lazily, so the wallet boots today but throws the first time a transaction flow needs gas estimates; wiring `GasFeeController` supplies that handler. Adds a required `instanceOptions.gasFeeController` option whose `clientId` (sent as `X-Client-Id` to the gas API) is required, so every client identifies itself; all other fields are optional and fall back to platform-agnostic defaults. wallet-cli passes `clientId: 'cli'`. The instance builds `getProvider` / `getCurrentNetworkEIP1559Compatibility` as lazy closures over `NetworkController`, and `GasFeeController` was made initialization-order-agnostic upstream (#9569), so no dependency-ordering machinery is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sirtimid
added a commit
that referenced
this pull request
Jul 21, 2026
Wire `GasFeeController` into `@metamask/wallet`'s default initialization set as its own `InitializationConfiguration`, mirroring the `transaction-controller` / `network-controller` instances. A wired `TransactionController` delegates `GasFeeController:fetchGasFeeEstimates` lazily, so the wallet boots today but throws the first time a transaction flow needs gas estimates; wiring `GasFeeController` supplies that handler. Adds a required `instanceOptions.gasFeeController` option whose `clientId` (sent as `X-Client-Id` to the gas API) is required, so every client identifies itself; all other fields are optional and fall back to platform-agnostic defaults. wallet-cli passes `clientId: 'cli'`. The instance builds `getProvider` / `getCurrentNetworkEIP1559Compatibility` as lazy closures over `NetworkController`, and `GasFeeController` was made initialization-order-agnostic upstream (#9569), so no dependency-ordering machinery is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Explanation
The
GasFeeControllerconstructor was the only currently-wired-or-wireable controller that performed eager, construction-time reads ofNetworkControllerand the network provider:new EthQuery(this.#getProvider())— unconditional.currentChainId— either via an eagergetChainId()call or viaNetworkController:getState+getNetworkClientById.This forced any shared initialization (e.g.
@metamask/wallet) to guaranteeNetworkControlleris constructed beforeGasFeeController, which is exactly the construction-order coupling we want to avoid. Every other order-agnostic controller resolves its collaborators lazily.This PR defers those reads to first use so the controller can be constructed in any order, matching the pattern already used elsewhere. No public API change — the constructor signature and gas-fee-fetching behavior are unchanged.
Changes
EthQuerybuild and the eagercurrentChainIdseeding from the constructor. Network-change subscription wiring is kept as-is (subscribing is safe regardless of construction order).#getEthQuery()— builds theEthQueryfromgetProvideron first use.#getCurrentChainId()— resolves the chain ID from thegetChainIdcallback when provided (only when paired withonNetworkDidChange, preserving the original coupling), otherwise fromNetworkController.#onNetworkControllerDidChangenow resetsethQuerytoundefined(rebuilt lazily on the next fetch) instead of rebuilding it eagerly per network change, and still updatescurrentChainId.Testing
NetworkController:getState/getNetworkClientById/getProvider/getChainIdcalls — even when those handlers throw — for both the callback and no-callback constructor branches.onNetworkDidChangecallback and theNetworkController:networkDidChangemessenger subscription (chain ID + eth query update on the next fetch).References
Splits out the root-cause fix from the wiring PR #9527, so that
GasFeeControllercan be migrated into the shared@metamask/walletinit set without a dependency-ordering mechanism.🤖 Generated with Claude Code
Note
Medium Risk
Touches core gas-estimation initialization and network-switch caching; behavior is intended to stay the same but timing of first network reads shifts to first fetch.
Overview
GasFeeControllerno longer touchesNetworkControlleror the RPC provider during construction, so it can be instantiated before the network stack is ready (e.g. shared@metamask/walletinit) without changing the public constructor or fetch semantics.The constructor drops eager
EthQuerycreation and immediate chain-ID resolution (getChainId()/getState+getNetworkClientById). Instead,#getEthQuery()and#getCurrentChainId()memoize provider and chain ID on first use inside_fetchGasFeeEstimateData. WhenonNetworkDidChange+getChainIdare supplied, the callback is stored without callinggetChainIdat construct time.On network change,
#onNetworkControllerDidChangeclears the cachedethQuery(rebuilt on the next fetch) and still updatescurrentChainIdand polling. Tests lock in zero constructor-time network/provider/getChainIdcalls and correct chain ID + provider refresh after network switches via callback orNetworkController:networkDidChange.Reviewed by Cursor Bugbot for commit 66c249d. Bugbot is set up for automated code reviews on this repo. Configure here.