Ethereum JSON-RPC Middleware for json-rpc-engine

raw JSON →
9.0.1 verified Thu Apr 23 auth: no javascript

eth-json-rpc-middleware provides a collection of Ethereum-specific middleware functions designed to integrate with `@metamask/json-rpc-engine`. It facilitates common RPC patterns like block tracking, transaction handling, and EIP-standard implementations (e.g., EIP-5792, EIP-7715). Maintained by MetaMask, this library is actively developed, evidenced by its frequent major releases and updates to support the latest Ethereum specifications. The current stable version is v20.0.0, which notably introduced separate CommonJS and ESM distributions. Its key differentiators include tight integration within the MetaMask ecosystem and robust support for advanced wallet and RPC interactions, making it a foundational component for building dApps and Ethereum tooling.

error ERR_PACKAGE_PATH_NOT_EXPORTED
cause Attempting to import modules using paths that are no longer exposed directly (e.g., `eth-json-rpc-middleware/dist/something.js`) after v20.0.0's change to `package.json` `exports`.
fix
Update imports to use the top-level package name and named imports (e.g., import { createSomeMiddleware } from 'eth-json-rpc-middleware';).
error TypeError: createFetchMiddleware is not a function
cause Calling `createFetchMiddleware` with an outdated signature (e.g., passing `rpcUrl` directly) after the v18.0.0 breaking change.
fix
Ensure createFetchMiddleware is called with an object containing an rpcService property, which itself has a provider.request method, as required since v18.0.0.
error Error: PollingBlockTracker - encountered an error while attempting to update latest block
cause An error occurred during an RPC call to fetch the latest block, and this error is being wrapped by the `PollingBlockTracker`.
fix
Inspect the underlying RPC error; if using older versions, be aware that getLatestBlock might reject on errors now, which could be an upstream issue with your RPC provider or network configuration.
breaking Version 20.0.0 introduced separate CommonJS and ESM distributions using the `exports` field in `package.json`. This breaks previously valid direct imports (e.g., from `dist/`) and may require updating `require()` statements to named imports.
fix Migrate CommonJS `require` statements to ESM `import` statements (e.g., `import { Name } from 'eth-json-rpc-middleware';`). Ensure your build system supports `package.json` `exports`.
breaking The `createFetchMiddleware` signature changed significantly in v18.0.0. It no longer accepts `fetch`, `btoa`, `rpcUrl`, or `originHttpHeaderKey` directly. Instead, it now strictly requires an `rpcService` object, typically sourced from `@metamask/network-controller`.
fix Refactor `createFetchMiddleware` initialization to pass an `rpcService` object. For example: `createFetchMiddleware({ rpcService: { provider: { request: async () => {} } } })`.
breaking Version 17.0.0 updated support for EIP-5792 to version 2.0.0, introducing changes like the `atomicRequired` property, making `from` optional in `SendCallsStruct`, and modifying error codes. This may require updates to dApps implementing EIP-5792.
fix Review and update your EIP-5792 related data structures and validation logic to align with the 2.0.0 specification changes, particularly for `SendCallsStruct` and `GetCallsStatusResult`.
gotcha Prior to v16.0.1, `fetch` middleware could treat non-standard JSON-RPC error responses (those with an `error` field but unexpected additional properties) as successful. This could lead to incorrect error handling or silent failures.
fix Upgrade to v16.0.1 or newer to ensure non-standard JSON-RPC error responses are correctly processed as errors.
gotcha Changes in `PollingBlockTracker.getLatestBlock()` behavior in v19.0.1 and v17.0.1 (regarding `useCache` and error rejection vs. hanging) could affect dApps relying on specific block tracking update mechanisms.
fix Test block tracking intensive parts of your application after upgrading. Be aware that `getLatestBlock` will now reject on errors rather than potentially hanging, and `useCache: false` is no longer included in some calls.
npm install eth-json-rpc-middleware
yarn add eth-json-rpc-middleware
pnpm add eth-json-rpc-middleware

This quickstart demonstrates how to set up a `json-rpc-engine` with common `eth-json-rpc-middleware` components, including `createFetchMiddleware`, `createBlockRefMiddleware`, and `createChainIdMiddleware`. It includes a mock RPC service, essential since `createFetchMiddleware` v18.0.0, to illustrate request handling.

import { JsonRpcEngine } from 'json-rpc-engine';
import { createFetchMiddleware, createBlockRefMiddleware, createChainIdMiddleware } from 'eth-json-rpc-middleware';

// Mock RPC service for createFetchMiddleware (required since v18.0.0)
const mockRpcService = {
  provider: {
    request: async ({ method, params }) => {
      console.log(`Mock RPC request: ${method} ${JSON.stringify(params)}`);
      // In a real application, this would forward to an actual RPC endpoint
      // using a provider from @metamask/network-controller or similar.
      if (method === 'eth_chainId') return '0x1'; // Mainnet
      if (method === 'eth_blockNumber') return '0x' + (123456789).toString(16);
      if (method === 'eth_sendRawTransaction') return '0xmockTxHash';
      return null; // Handle other methods as needed
    },
  },
  // Other properties like 'currentChainId' might be expected by some versions/integrations
};

async function setupAndUseRpcEngine() {
  const engine = new JsonRpcEngine();

  // Add middleware
  engine.add(createBlockRefMiddleware());
  engine.add(createChainIdMiddleware('0x1')); // Example: force chainId to Ethereum Mainnet
  engine.add(createFetchMiddleware({ rpcService: mockRpcService }));

  // Example request
  try {
    const response = await engine.handle({ id: 1, jsonrpc: '2.0', method: 'eth_blockNumber' });
    console.log('eth_blockNumber response:', response.result);

    const chainIdResponse = await engine.handle({ id: 2, jsonrpc: '2.0', method: 'eth_chainId' });
    console.log('eth_chainId response:', chainIdResponse.result);

    // Example of a request that might pass through the fetch middleware
    const sendTxResponse = await engine.handle({
      id: 3,
      jsonrpc: '2.0',
      method: 'eth_sendRawTransaction',
      params: ['0xmockrawtransactiondata'],
    });
    console.log('eth_sendRawTransaction response:', sendTxResponse.result);

  } catch (error) {
    console.error('RPC Error:', error);
  }
}

setupAndUseRpcEngine();