Skip to content

[connect-evm] 2x bundle size increase over @metamask/sdk #255

Description

@iPaulPro

@metamask/connect-evm bundle is around 2× larger than @metamask/sdk due to unrelated transitive dependencies

Package: @metamask/connect-evm@0.9.1


Summary

Migrating from @metamask/sdk to @metamask/connect-evm produces a nearly increase in minified chunk size with no change in runtime behavior. I created a minimal project following only the instructions at https://docs.metamask.io/metamask-connect/evm/quickstart/wagmi/ for both sdk and connect-evm. The difference in chunks was as follows:

Package Chunk size (minified)
@metamask/sdk 542.12 kB
@metamask/connect-evm 1026.73 kB

Root cause

@metamask/connect-evm is architecturally a thin EIP-1193 adapter over @metamask/connect-multichain; every session, transport, and RPC call is delegated through createMultichainClient, and MetamaskConnectEVM wraps a MultichainCore instance directly. This means the entire connect-multichain dependency graph is transitively bundled into EVM-only consumers, including any multichain-specific code that is never exercised in that context.

The coupling runs deeper than the top-level class. The EIP1193Provider implementation imports EventEmitter and RPCInvokeMethodErr directly from @metamask/connect-multichain. These are generic primitives, a base event emitter and an RPC error class, that have no inherent relationship to multichain functionality. Their presence inside connect-multichain is a strong signal that the shared foundation has never been properly separated from protocol-specific code.

This is further evidenced by EIP1193Provider accessing MultichainCore's options property via an as any type assertion, with an accompanying TODO noting it requires refactoring. connect-evm is reaching into connect-multichain internals through an unofficial channel because the public API surface of connect-multichain was never designed to support connect-evm being layered on top of it. This is an architectural boundary problem, not just a dependency graph one.

Additionally, @metamask/chain-agnostic-permission is imported solely for parseScopeString, which is called once in disconnect() to check whether a scope's namespace prefix equals "eip155", a trivial string operation that does not justify pulling in an entire package.

Finally, @metamask/analytics is deeply integrated into the request lifecycle, with analytics.track(...) called on every request, success, rejection, and failure path throughout the class. This makes it impossible to tree-shake without a build-time module alias override, forcing consumers who do not want telemetry to maintain external workarounds (e.g., a Vite/Rollup resolve.alias stub).


Expected behaviour

A package named connect-evm should have a dependency graph constrained to EVM connectivity primitives only. The shared transport, session management, storage adapters, event emitter, and RPC error types that are currently embedded inside connect-multichain should be extracted into a connect-core (or equivalent) base package with a stable, intentionally designed public API. Both connect-evm and connect-multichain would then depend on connect-core, allowing EVM-only consumers to avoid bundling any multichain-specific code entirely, and eliminating the need for as any casts across the abstraction boundary.


Suggested remediation

  1. Extract shared primitives: EventEmitter, RPCInvokeMethodErr, provider transport, session lifecycle, storage adapters, and CAIP scope utilities into a @metamask/connect-core package with an explicit, stable public API.
  2. Rebuild @metamask/connect-evm on top of @metamask/connect-core rather than @metamask/connect-multichain, so the multichain-specific dependency graph is not transitively included and the as any internal access can be replaced with first-class API surface.
  3. Remove @metamask/chain-agnostic-permission as a dependency of connect-evm. The single usage (parseScopeString for an eip155 namespace prefix check) should either be inlined as a string operation or provided by connect-core as a lightweight utility.
  4. Declare @metamask/analytics as an optional peer dependency, or place it behind a side-effect-free export condition, so consumers are not forced to ship telemetry code or maintain build-tool workarounds to suppress it.

Impact

This bundle bloat directly degrades Lighthouse/Core Web Vitals scores (TTI, TBT), increases cold-start parse and compile time in V8/SpiderMonkey, and imposes unnecessary bandwidth costs on end users, particularly on mobile and constrained connections.


EDIT

The drastic change in bundle size originally reported was not only related to MetaMask, but also changes to Rolldown in Vite 8.x. After reverting to 3.5.0 and rebuilding I'm now seeing the metamask bundle at around 540kB. To isolate more properly, I created minimal reproduction projects building with vite 8.0.4, and the difference is closer to 2x. All of the rest above is still valid, and 2x increase is unreasonable. The full list of dependencies:

  "dependencies": {
    "@metamask/connect-evm": "^0.9.1",
    "@tanstack/react-query": "^5.97.0",
    "react": "^19.2.4",
    "react-dom": "^19.2.4",
    "viem": "~2.47.11",
    "wagmi": "3.6.1"
  },

vs

  "dependencies": {
    "@metamask/sdk": "^0.34.0",
    "@tanstack/react-query": "^5.97.0",
    "react": "^19.2.4",
    "react-dom": "^19.2.4",
    "viem": "~2.47.11",
    "wagmi": "3.5.0"
  },

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions