diff --git a/packages/web3-core/src/types.ts b/packages/web3-core/src/types.ts
index 3f4e66d1f52..890a5e20c5a 100644
--- a/packages/web3-core/src/types.ts
+++ b/packages/web3-core/src/types.ts
@@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import { HexString, Transaction } from 'web3-types';
+import { HexString, JsonRpcResponse, Transaction, Web3APIMethod, Web3APIRequest, Web3APIReturnType } from 'web3-types';
export type TransactionTypeParser = (
transaction: Transaction,
@@ -30,3 +30,18 @@ export interface ExtensionObject {
property?: string;
methods: Method[];
}
+
+export interface RequestManagerMiddleware {
+ processRequest<
+ AnotherMethod extends Web3APIMethod
+ >(
+ request: Web3APIRequest,
+ options?: { [key: string]: unknown }): Promise>;
+
+ processResponse<
+ AnotherMethod extends Web3APIMethod,
+ ResponseType = Web3APIReturnType>
+ (
+ response: JsonRpcResponse,
+ options?: { [key: string]: unknown }): Promise>;
+ }
\ No newline at end of file
diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts
index 76afcaf8875..b50f8ba30f7 100644
--- a/packages/web3-core/src/web3_context.ts
+++ b/packages/web3-core/src/web3_context.ts
@@ -25,7 +25,7 @@ import { isNullish } from 'web3-utils';
import { BaseTransaction, TransactionFactory } from 'web3-eth-accounts';
import { isSupportedProvider } from './utils.js';
// eslint-disable-next-line import/no-cycle
-import { ExtensionObject } from './types.js';
+import { ExtensionObject, RequestManagerMiddleware } from './types.js';
import { Web3BatchRequest } from './web3_batch_request.js';
// eslint-disable-next-line import/no-cycle
import { Web3Config, Web3ConfigEvent, Web3ConfigOptions } from './web3_config.js';
@@ -65,6 +65,7 @@ export type Web3ContextInitOptions<
registeredSubscriptions?: RegisteredSubs;
accountProvider?: Web3AccountProvider;
wallet?: Web3BaseWallet;
+ requestManagerMiddleware?: RequestManagerMiddleware;
};
// eslint-disable-next-line no-use-before-define
@@ -129,6 +130,7 @@ export class Web3Context<
registeredSubscriptions,
accountProvider,
wallet,
+ requestManagerMiddleware
} = providerOrContext as Web3ContextInitOptions;
this.setConfig(config ?? {});
@@ -138,6 +140,7 @@ export class Web3Context<
new Web3RequestManager(
provider,
config?.enableExperimentalFeatures?.useSubscriptionWhenCheckingBlockTimeout,
+ requestManagerMiddleware
);
if (subscriptionManager) {
@@ -352,6 +355,11 @@ export class Web3Context<
this.provider = provider;
return true;
}
+
+ public setRequestManagerMiddleware(requestManagerMiddleware: RequestManagerMiddleware){
+ this.requestManager.setMiddleware(requestManagerMiddleware);
+ }
+
/**
* Will return the {@link Web3BatchRequest} constructor.
*/
diff --git a/packages/web3-core/src/web3_request_manager.ts b/packages/web3-core/src/web3_request_manager.ts
index 3c6cabd0182..dc12d919fd0 100644
--- a/packages/web3-core/src/web3_request_manager.ts
+++ b/packages/web3-core/src/web3_request_manager.ts
@@ -52,6 +52,7 @@ import {
isWeb3Provider,
} from './utils.js';
import { Web3EventEmitter } from './web3_event_emitter.js';
+import { RequestManagerMiddleware } from './types.js';
export enum Web3RequestManagerEvent {
PROVIDER_CHANGED = 'PROVIDER_CHANGED',
@@ -73,9 +74,12 @@ export class Web3RequestManager<
}> {
private _provider?: SupportedProviders;
private readonly useRpcCallSpecification?: boolean;
+ public middleware?: RequestManagerMiddleware;
+
public constructor(
provider?: SupportedProviders | string,
useRpcCallSpecification?: boolean,
+ requestManagerMiddleware?: RequestManagerMiddleware
) {
super();
@@ -83,8 +87,12 @@ export class Web3RequestManager<
this.setProvider(provider);
}
this.useRpcCallSpecification = useRpcCallSpecification;
- }
+ if (!isNullish(requestManagerMiddleware))
+ this.middleware = requestManagerMiddleware;
+
+ }
+
/**
* Will return all available providers
*/
@@ -142,6 +150,10 @@ export class Web3RequestManager<
return true;
}
+ public setMiddleware(requestManagerMiddleware: RequestManagerMiddleware){
+ this.middleware = requestManagerMiddleware;
+ }
+
/**
*
* Will execute a request
@@ -155,7 +167,17 @@ export class Web3RequestManager<
Method extends Web3APIMethod,
ResponseType = Web3APIReturnType,
>(request: Web3APIRequest): Promise {
- const response = await this._sendRequest(request);
+
+ let requestObj = {...request};
+
+ if (!isNullish(this.middleware))
+ requestObj = await this.middleware.processRequest(requestObj);
+
+ let response = await this._sendRequest(requestObj);
+
+ if (!isNullish(this.middleware))
+ response = await this.middleware.processResponse(response);
+
if (jsonRpc.isResponseWithResult(response)) {
return response.result;
}
diff --git a/packages/web3-core/test/unit/web3_context.test.ts b/packages/web3-core/test/unit/web3_context.test.ts
index 3c433f7259a..186122df54e 100644
--- a/packages/web3-core/test/unit/web3_context.test.ts
+++ b/packages/web3-core/test/unit/web3_context.test.ts
@@ -18,8 +18,10 @@ along with web3.js. If not, see .
// eslint-disable-next-line max-classes-per-file
import { ExistingPluginNamespaceError } from 'web3-errors';
import HttpProvider from 'web3-providers-http';
+import { EthExecutionAPI, JsonRpcResponse, Web3APIMethod, Web3APIRequest, Web3APIReturnType } from 'web3-types';
import { Web3Context, Web3PluginBase } from '../../src/web3_context';
import { Web3RequestManager } from '../../src/web3_request_manager';
+import { RequestManagerMiddleware } from '../../src/types';
// eslint-disable-next-line @typescript-eslint/ban-types
class Context1 extends Web3Context<{}> {}
@@ -63,6 +65,19 @@ describe('Web3Context', () => {
expect(context.currentProvider).toBeInstanceOf(HttpProvider);
});
+
+ it('should set middleware for the request manager', () => {
+ const context = new Web3Context('http://test.com');
+
+ const middleware: RequestManagerMiddleware
+ = {
+ processRequest: jest.fn(async >(request: Web3APIRequest) => request),
+ processResponse: jest.fn(async , ResponseType = Web3APIReturnType>(response: JsonRpcResponse) => response),
+ };
+
+ context.setRequestManagerMiddleware(middleware);
+ expect(context.requestManager.middleware).toEqual(middleware);
+ });
});
describe('getContextObject', () => {
diff --git a/packages/web3-core/test/unit/web3_middleware_request_manager.test.ts b/packages/web3-core/test/unit/web3_middleware_request_manager.test.ts
new file mode 100644
index 00000000000..d240831d91b
--- /dev/null
+++ b/packages/web3-core/test/unit/web3_middleware_request_manager.test.ts
@@ -0,0 +1,144 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import { EthExecutionAPI, JsonRpcResponse, Web3APIMethod, Web3APIRequest, Web3APIReturnType } from 'web3-types';
+import { jsonRpc } from 'web3-utils';
+import { RequestManagerMiddleware } from '../../src/types';
+import { Web3RequestManager } from '../../src/web3_request_manager';
+
+class Web3Middleware implements RequestManagerMiddleware {
+
+ // eslint-disable-next-line class-methods-use-this
+ public async processRequest>(
+ request: Web3APIRequest
+ ): Promise> {
+ // Implement the processRequest logic here
+
+ let requestObj = {...request};
+ if (request.method === 'eth_call' && Array.isArray(request.params)) {
+ requestObj = {
+ ...requestObj,
+ params: [...request.params, '0x0', '0x1'],
+ };
+ }
+
+ return Promise.resolve(requestObj);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ public async processResponse<
+ Method extends Web3APIMethod,
+ ResponseType = Web3APIReturnType
+ >(
+ response: JsonRpcResponse
+ ): Promise> {
+
+ let responseObj = {...response};
+ if (!jsonRpc.isBatchResponse(responseObj) && responseObj.id === 1) {
+ responseObj = {
+ ...responseObj,
+ result: '0x6a756e616964' as any,
+ };
+ }
+
+ return Promise.resolve(responseObj);
+ }
+}
+
+describe('Request Manager Middleware', () => {
+ let requestManagerMiddleware: RequestManagerMiddleware;
+
+ beforeAll(() => {
+ requestManagerMiddleware = {
+ processRequest: jest.fn(async >(request: Web3APIRequest) => request),
+ processResponse: jest.fn(async , ResponseType = Web3APIReturnType>(response: JsonRpcResponse) => response),
+ };
+
+ });
+
+ it('should set requestManagerMiddleware via constructor', () => {
+ const web3RequestManager1: Web3RequestManager = new Web3RequestManager(undefined, true, requestManagerMiddleware);
+
+ expect(web3RequestManager1.middleware).toBeDefined();
+ expect(web3RequestManager1.middleware).toEqual(requestManagerMiddleware);
+ });
+
+ it('should set requestManagerMiddleware via set method', () => {
+
+ const middleware2: RequestManagerMiddleware = new Web3Middleware();
+ const web3RequestManager2: Web3RequestManager = new Web3RequestManager('http://localhost:8181');
+ web3RequestManager2.setMiddleware(middleware2);
+
+ expect(web3RequestManager2.middleware).toBeDefined();
+ expect(web3RequestManager2.middleware).toEqual(middleware2);
+ });
+
+ it('should call processRequest and processResponse functions of requestManagerMiddleware', async () => {
+
+ const web3RequestManager3 = new Web3RequestManager('http://localhost:8080', true, requestManagerMiddleware );
+
+ const expectedResponse: JsonRpcResponse = {
+ jsonrpc: '2.0',
+ id: 1,
+ result: '0x0',
+ };
+
+ jest.spyOn(web3RequestManager3 as any, '_sendRequest').mockResolvedValue(expectedResponse);
+
+ const request = {
+ id: 1,
+ method: 'eth_call',
+ params: [],
+ };
+
+ await web3RequestManager3.send(request);
+
+ expect(requestManagerMiddleware.processRequest).toHaveBeenCalledWith(request);
+ expect(requestManagerMiddleware.processResponse).toHaveBeenCalled();
+ });
+
+ it('should allow modification of request and response', async () => {
+
+ const middleware3: RequestManagerMiddleware = new Web3Middleware();
+
+ const web3RequestManager3 = new Web3RequestManager('http://localhost:8080', true, middleware3);
+
+ const expectedResponse: JsonRpcResponse = {
+ jsonrpc: '2.0',
+ id: 1,
+ result: '0x0',
+ };
+
+ const mockSendRequest = jest.spyOn(web3RequestManager3 as any, '_sendRequest');
+ mockSendRequest.mockResolvedValue(expectedResponse);
+
+ const request = {
+ id: 1,
+ method: 'eth_call',
+ params: ['0x3'],
+ };
+
+ const response = await web3RequestManager3.send(request);
+ expect(response).toBe('0x6a756e616964');
+
+ expect(mockSendRequest).toHaveBeenCalledWith({
+ ...request,
+ params: [...request.params, '0x0', '0x1'],
+ });
+
+ });
+});
diff --git a/tools/web3-plugin-example/src/custom_rpc_methods.ts b/tools/web3-plugin-example/src/custom_rpc_methods.ts
index 101816a9c27..ed1c2fbee23 100644
--- a/tools/web3-plugin-example/src/custom_rpc_methods.ts
+++ b/tools/web3-plugin-example/src/custom_rpc_methods.ts
@@ -17,6 +17,8 @@ along with web3.js. If not, see .
import { Web3PluginBase } from 'web3-core';
// eslint-disable-next-line require-extensions/require-extensions
import { Web3Context } from './reexported_web3_context';
+// eslint-disable-next-line require-extensions/require-extensions
+import { Web3Middleware } from './middleware';
type CustomRpcApi = {
custom_rpc_method: () => string;
@@ -25,6 +27,24 @@ type CustomRpcApi = {
export class CustomRpcMethodsPlugin extends Web3PluginBase {
public pluginNamespace = 'customRpcMethods';
+ public web3Middleware: Web3Middleware | undefined;
+
+ public constructor(testMiddleware = false) {
+ super();
+
+ if (testMiddleware) {
+ this.web3Middleware = new Web3Middleware();
+ }
+ }
+
+ public link(parentContext: Web3Context): void {
+
+ if (this.web3Middleware)
+ parentContext.requestManager.setMiddleware(this.web3Middleware);
+
+ super.link(parentContext);
+ }
+
public async customRpcMethod() {
return this.requestManager.send({
@@ -39,6 +59,7 @@ export class CustomRpcMethodsPlugin extends Web3PluginBase {
params: [parameter1, parameter2],
});
}
+
}
// Module Augmentation
diff --git a/tools/web3-plugin-example/src/middleware.ts b/tools/web3-plugin-example/src/middleware.ts
new file mode 100644
index 00000000000..3af435fd025
--- /dev/null
+++ b/tools/web3-plugin-example/src/middleware.ts
@@ -0,0 +1,61 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { RequestManagerMiddleware } from "web3-core";
+import { Web3APIMethod, Web3APIRequest, Web3APIReturnType, JsonRpcResponse } from "web3-types";
+import { jsonRpc } from "web3-utils";
+
+export class Web3Middleware implements RequestManagerMiddleware {
+
+ // eslint-disable-next-line class-methods-use-this
+ public async processRequest>(
+ request: Web3APIRequest
+ ): Promise> {
+
+ // add your custom logic here for processing requests
+ let reqObj = {...request};
+ if (reqObj.method === 'eth_call' && Array.isArray(reqObj.params)) {
+ reqObj = {
+ ...reqObj,
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ params: [...reqObj.params, '0x0', '0x1'],
+ };
+ }
+
+ return Promise.resolve(reqObj);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ public async processResponse<
+ Method extends Web3APIMethod,
+ ResponseType = Web3APIReturnType
+ >(
+ response: JsonRpcResponse
+ ): Promise> {
+
+ // add your custom logic here for processing responses, following is just a demo
+ let resObj = {...response};
+ if (!jsonRpc.isBatchResponse(resObj) && resObj.id === 1) {
+ resObj = {
+ ...resObj,
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ result: '0x6a756e616964' as any,
+ };
+ }
+
+ return Promise.resolve(resObj);
+ }
+}
\ No newline at end of file
diff --git a/tools/web3-plugin-example/test/unit/middleware.test.ts b/tools/web3-plugin-example/test/unit/middleware.test.ts
new file mode 100644
index 00000000000..717ddc8856d
--- /dev/null
+++ b/tools/web3-plugin-example/test/unit/middleware.test.ts
@@ -0,0 +1,66 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import Web3, { JsonRpcResponse, TransactionCall } from 'web3';
+import { CustomRpcMethodsPlugin } from '../../src/custom_rpc_methods';
+
+
+describe('CustomRpcMethodsPlugin Middleware', () => {
+ it('should modify request and response using middleware plugin', async () => {
+
+ const web3 = new Web3('http://127.0.0.1:8545');
+ const plugin = new CustomRpcMethodsPlugin(true);
+
+ // Test mocks and spy - code block start
+ const expectedResponse: JsonRpcResponse = {
+ jsonrpc: '2.0',
+ id: 1,
+ result: '0x0',
+ };
+
+ const mockSendRequest = jest.spyOn(web3.requestManager as any, '_sendRequest');
+ mockSendRequest.mockResolvedValue(expectedResponse);
+ // Test mocks and spy - code block end
+
+ web3.registerPlugin(plugin);
+
+ const transaction: TransactionCall = {
+ from: '0xee815C0a7cD0Ab35273Bc5943a3c6839a680Eaf0',
+ to: '0xe3342ae375e9B02F7D5513a1BB2276438D193e15',
+ type: '0x0',
+ data: '0x',
+ nonce: '0x4',
+ chain: 'mainnet',
+ hardfork: 'berlin',
+ chainId: '0x1',
+ };
+ const result = await web3.eth.call(transaction);
+ expect(result).toBe('0x6a756e616964'); // result modified by response processor , so its 0x6a756e616964 instead of 0x0
+
+ const expectedCall = {
+ method: "eth_call",
+ params: [
+ {...transaction},
+ "latest",
+ "0x0", // added by middleware by request processor
+ "0x1", // added by middleware by request processor
+ ],
+ };
+ expect(mockSendRequest).toHaveBeenCalledWith(expectedCall);
+
+ });
+});