Skip to content

feat: present a bearer token on requests to the built-in Infura endpoints - #10252

Draft
cryptodev-2s wants to merge 3 commits into
mainfrom
feat/rpc-service-dynamic-request-headers
Draft

cryptodev-2s wants to merge 3 commits into
mainfrom
feat/rpc-service-dynamic-request-headers

Conversation

@cryptodev-2s

@cryptodev-2s cryptodev-2s commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Explanation

Requests to our built-in Infura endpoints should carry a bearer token in the Authorization header. The token identifies the user and can be refreshed at any time, so it has to be resolved per request rather than captured when a network client is built.

RpcService had no way to do that. It accepts a fetch and a static fetchOptions, and request takes per-call options, but nothing resolves a header value at the moment a request goes out. The obvious workaround is to hand RpcService a wrapped fetch that mutates the options on the way through, and that does work, but it pays for a header with a transport substitution: a caller who supplies their own fetch silently gets a different function than the one they passed, and the wrapper has to make assumptions about the shape of init.headers from outside the class that owns it.

This PR adds the missing capability at the HTTP boundary instead, then uses it.

1. A dynamic header hook on RpcService

getRequestHeaders?: () => Promise<Record<string, string> | undefined>;

Called immediately before each fetch, inside the Cockatiel policy, so every retry attempt resolves headers again. Precedence is getRequestHeaders > request options > fetchOptions > defaults. A rejection fails the request attempt; resolve undefined to proceed without the headers.

Two deliberate details:

  • Resolution happens after the REQUEST INITIATED debug log, which already receives the full fetch options. Anything this hook returns is a good candidate for being sensitive, so injecting afterwards keeps it out of loglevel output.
  • Merging uses the same deepmerge the class already uses to fold in the Basic credential derived from URL userinfo. No second assumption about header shape, and no assumption made from outside the class.

2. NetworkController.getInfuraAuthToken

getInfuraAuthToken?: () => Promise<string | undefined>;

Core owns the gating, the client owns the token.

Core attaches the credential only when configuration.type === 'infura' and the endpoint is not a failover. That is the security-critical half and it keys on the client type and the failover flag, not on the URL, so it cannot be fooled by an endpoint that merely looks like Infura. A custom endpoint pointing at Infura with somebody else's key gets nothing, failover endpoints get nothing, and under forced failover mode there is no Infura endpoint in the chain so nothing is issued at all.

The client supplies where the token comes from, which keeps the auth controller and the consent policy out of network-controller. No new messenger action, no new dependency, nothing breaking. Clients that pass nothing behave exactly as before.

Because an RpcService is bound to a single endpointUrl fixed at construction, a header set for one endpoint cannot reach another. This is also why the hook belongs on the constructor rather than on request: RpcServiceChain.request forwards one options object to every service in the chain, so a credential passed that way would also be sent to the failovers.

Client usage

new NetworkController({
  state,
  messenger,
  infuraProjectId,
  getInfuraAuthToken: async () => {
    try {
      // The token identifies the user, so only send it with consent.
      if (!analyticsController.state.optedIn) {
        return undefined;
      }
      return await authenticationController.getBearerToken();
    } catch {
      // Throws while the wallet is locked. Proceed without the credential.
      return undefined;
    }
  },
});

Resolve the token inside the callback, never in a closure around it. getInfuraAuthToken is invoked per request; anything read at construction is stale for the life of the client.

References

N/A

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, highlighting breaking changes as necessary
  • I've prepared solutions for platform tests, or opened PRs on platform repositories to handle them

@cryptodev-2s

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@10.0.1-preview-4454eb0
@metamask-previews/accounts-controller@40.0.0-preview-4454eb0
@metamask-previews/address-book-controller@8.0.0-preview-4454eb0
@metamask-previews/ai-controllers@2.0.0-preview-4454eb0
@metamask-previews/analytics-controller@3.0.0-preview-4454eb0
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-4454eb0
@metamask-previews/announcement-controller@9.0.0-preview-4454eb0
@metamask-previews/app-metadata-controller@3.0.0-preview-4454eb0
@metamask-previews/approval-controller@10.0.0-preview-4454eb0
@metamask-previews/assets-controller@16.1.0-preview-4454eb0
@metamask-previews/assets-controllers@112.0.2-preview-4454eb0
@metamask-previews/authenticated-user-storage@4.0.0-preview-4454eb0
@metamask-previews/base-controller@10.0.0-preview-4454eb0
@metamask-previews/base-data-service@2.0.0-preview-4454eb0
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-4454eb0
@metamask-previews/bridge-controller@81.1.0-preview-4454eb0
@metamask-previews/bridge-status-controller@76.2.0-preview-4454eb0
@metamask-previews/build-utils@4.0.0-preview-4454eb0
@metamask-previews/chain-agnostic-permission@2.0.0-preview-4454eb0
@metamask-previews/chomp-api-service@5.0.0-preview-4454eb0
@metamask-previews/claims-controller@1.0.1-preview-4454eb0
@metamask-previews/client-controller@2.0.0-preview-4454eb0
@metamask-previews/client-utils@3.0.1-preview-4454eb0
@metamask-previews/compliance-controller@3.0.0-preview-4454eb0
@metamask-previews/composable-controller@13.0.0-preview-4454eb0
@metamask-previews/config-registry-controller@4.0.0-preview-4454eb0
@metamask-previews/connectivity-controller@1.0.0-preview-4454eb0
@metamask-previews/controller-utils@13.0.0-preview-4454eb0
@metamask-previews/core-backend@11.0.0-preview-4454eb0
@metamask-previews/cryptography@0.0.0-preview-4454eb0
@metamask-previews/delegation-controller@4.0.0-preview-4454eb0
@metamask-previews/earn-controller@13.0.0-preview-4454eb0
@metamask-previews/eip-5792-middleware@4.0.0-preview-4454eb0
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-4454eb0
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-4454eb0
@metamask-previews/eth-block-tracker@16.0.0-preview-4454eb0
@metamask-previews/eth-json-rpc-middleware@25.0.0-preview-4454eb0
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-4454eb0
@metamask-previews/foundryup@2.0.0-preview-4454eb0
@metamask-previews/gas-fee-controller@27.0.0-preview-4454eb0
@metamask-previews/gator-permissions-controller@6.0.0-preview-4454eb0
@metamask-previews/geolocation-controller@2.0.0-preview-4454eb0
@metamask-previews/java-tron-up@2.0.0-preview-4454eb0
@metamask-previews/json-rpc-engine@11.0.0-preview-4454eb0
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-4454eb0
@metamask-previews/keyring-controller@28.0.0-preview-4454eb0
@metamask-previews/kyc-controller@0.3.0-preview-4454eb0
@metamask-previews/local-node-utils@2.0.0-preview-4454eb0
@metamask-previews/logging-controller@10.0.0-preview-4454eb0
@metamask-previews/message-manager@15.0.0-preview-4454eb0
@metamask-previews/messenger@3.0.0-preview-4454eb0
@metamask-previews/messenger-cli@1.0.0-preview-4454eb0
@metamask-previews/money-account-api-data-service@1.0.0-preview-4454eb0
@metamask-previews/money-account-balance-service@3.0.0-preview-4454eb0
@metamask-previews/money-account-controller@2.0.0-preview-4454eb0
@metamask-previews/money-account-upgrade-controller@5.0.0-preview-4454eb0
@metamask-previews/money-account-utils@2.0.0-preview-4454eb0
@metamask-previews/multichain-account-service@14.0.0-preview-4454eb0
@metamask-previews/multichain-api-middleware@5.0.0-preview-4454eb0
@metamask-previews/multichain-network-controller@4.0.0-preview-4454eb0
@metamask-previews/multichain-transactions-controller@8.0.0-preview-4454eb0
@metamask-previews/name-controller@10.0.0-preview-4454eb0
@metamask-previews/network-connection-banner-controller@1.0.0-preview-4454eb0
@metamask-previews/network-controller@37.0.0-preview-4454eb0
@metamask-previews/network-enablement-controller@7.0.0-preview-4454eb0
@metamask-previews/notification-services-controller@28.0.1-preview-4454eb0
@metamask-previews/passkey-controller@4.0.0-preview-4454eb0
@metamask-previews/permission-controller@14.0.0-preview-4454eb0
@metamask-previews/permission-log-controller@6.0.0-preview-4454eb0
@metamask-previews/perps-controller@17.1.0-preview-4454eb0
@metamask-previews/phishing-controller@18.1.0-preview-4454eb0
@metamask-previews/platform-api-docs@0.2.0-preview-4454eb0
@metamask-previews/polling-controller@17.0.0-preview-4454eb0
@metamask-previews/preferences-controller@24.0.0-preview-4454eb0
@metamask-previews/profile-metrics-controller@5.1.0-preview-4454eb0
@metamask-previews/profile-sync-controller@32.1.1-preview-4454eb0
@metamask-previews/ramps-controller@22.0.0-preview-4454eb0
@metamask-previews/rate-limit-controller@8.0.0-preview-4454eb0
@metamask-previews/react-data-query@2.0.0-preview-4454eb0
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-4454eb0
@metamask-previews/sample-controllers@6.0.0-preview-4454eb0
@metamask-previews/seedless-onboarding-controller@11.0.0-preview-4454eb0
@metamask-previews/selected-network-controller@27.0.0-preview-4454eb0
@metamask-previews/sentinel-api-service@2.0.0-preview-4454eb0
@metamask-previews/shield-controller@7.0.1-preview-4454eb0
@metamask-previews/signature-controller@40.0.0-preview-4454eb0
@metamask-previews/smart-transactions-controller@27.0.1-preview-4454eb0
@metamask-previews/snap-account-service@3.0.0-preview-4454eb0
@metamask-previews/social-controllers@3.0.1-preview-4454eb0
@metamask-previews/solana-test-validator-up@2.0.0-preview-4454eb0
@metamask-previews/stellar-quickstart-up@0.0.0-preview-4454eb0
@metamask-previews/storage-service@2.0.0-preview-4454eb0
@metamask-previews/subscription-controller@9.0.1-preview-4454eb0
@metamask-previews/transaction-controller@70.0.1-preview-4454eb0
@metamask-previews/transaction-pay-controller@28.0.2-preview-4454eb0
@metamask-previews/user-operation-controller@42.0.0-preview-4454eb0
@metamask-previews/utils@12.0.0-preview-4454eb0
@metamask-previews/wallet@13.0.0-preview-4454eb0
@metamask-previews/wallet-cli@0.0.0-preview-4454eb0

@cryptodev-2s cryptodev-2s changed the title feat: resolve dynamic request headers at the RPC service transport layer feat: present a bearer token on requests to the built-in Infura endpoints Sep 15, 2026
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