diff --git a/packages/web3-core/src/web3_request_manager.ts b/packages/web3-core/src/web3_request_manager.ts index 5a1b0aacd45..47e943a16c1 100644 --- a/packages/web3-core/src/web3_request_manager.ts +++ b/packages/web3-core/src/web3_request_manager.ts @@ -274,6 +274,16 @@ export class Web3RequestManager< response: JsonRpcResponse, { legacy, error }: { legacy: boolean; error: boolean }, ): JsonRpcResponse | never { + if (isNullish(response)) { + return this._buildResponse( + payload, + // Some providers uses "null" as valid empty response + // eslint-disable-next-line no-null/no-null + null as unknown as JsonRpcResponse, + error, + ); + } + // This is the majority of the cases so check these first // A valid JSON-RPC response with error object if (jsonRpc.isResponseWithError(response)) { @@ -308,29 +318,7 @@ export class Web3RequestManager< !jsonRpc.isResponseWithError(response) && !jsonRpc.isResponseWithResult(response) ) { - const res = { - jsonrpc: '2.0', - // eslint-disable-next-line no-nested-ternary - id: jsonRpc.isBatchRequest(payload) - ? payload[0].id - : 'id' in payload - ? payload.id - : // Have to use the null here explicitly - // eslint-disable-next-line no-null/no-null - null, - }; - - if (error) { - return { - ...res, - error: response as unknown, - } as JsonRpcResponse; - } - - return { - ...res, - result: response as unknown, - } as JsonRpcResponse; + return this._buildResponse(payload, response, error); } if (jsonRpc.isBatchRequest(payload) && !Array.isArray(response)) { @@ -352,4 +340,36 @@ export class Web3RequestManager< throw new ResponseError(response, 'Invalid response'); } + + // Need to use same types as _processJsonRpcResponse so have to declare as instance method + // eslint-disable-next-line class-methods-use-this + private _buildResponse( + payload: JsonRpcPayload, + response: JsonRpcResponse, + error: boolean, + ): JsonRpcResponse { + const res = { + jsonrpc: '2.0', + // eslint-disable-next-line no-nested-ternary + id: jsonRpc.isBatchRequest(payload) + ? payload[0].id + : 'id' in payload + ? payload.id + : // Have to use the null here explicitly + // eslint-disable-next-line no-null/no-null + null, + }; + + if (error) { + return { + ...res, + error: response as unknown, + } as JsonRpcResponse; + } + + return { + ...res, + result: response as unknown, + } as JsonRpcResponse; + } } diff --git a/packages/web3-core/test/unit/web3_request_manager.test.ts b/packages/web3-core/test/unit/web3_request_manager.test.ts index ff30a0afb85..73f13e90ed0 100644 --- a/packages/web3-core/test/unit/web3_request_manager.test.ts +++ b/packages/web3-core/test/unit/web3_request_manager.test.ts @@ -280,10 +280,9 @@ describe('Web3RequestManager', () => { describe('web3-provider', () => { beforeEach(() => { + // isWeb3Provider uses instanceof to check if the provider is a Web3Provider + // So we have to mock the response jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(true); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(false); }); it('should pass request to provider and resolve if provider resolves it', async () => { @@ -318,14 +317,6 @@ describe('Web3RequestManager', () => { }); describe('legacy-request-provider', () => { - beforeEach(() => { - jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(true); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(false); - jest.spyOn(utils, 'isEIP1193Provider').mockReturnValue(false); - }); - it('should pass request to provider and resolve if provider resolves it', async () => { const manager = new Web3RequestManager(); const myProvider = { @@ -382,10 +373,8 @@ describe('Web3RequestManager', () => { describe('eip1193-provider', () => { beforeEach(() => { - jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(false); + // isEIP1193Provider uses typeof to check if the provider is a EIP1193Provider + // So we have to mock the response jest.spyOn(utils, 'isEIP1193Provider').mockReturnValue(true); }); @@ -439,10 +428,8 @@ describe('Web3RequestManager', () => { describe('eip1193-provider - return non json-rpc compliance response', () => { beforeEach(() => { - jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(false); + // isEIP1193Provider uses typeof to check if the provider is a EIP1193Provider + // So we have to mock the response jest.spyOn(utils, 'isEIP1193Provider').mockReturnValue(true); }); @@ -475,16 +462,41 @@ describe('Web3RequestManager', () => { expect(myProvider.request).toHaveBeenCalledTimes(1); expect(myProvider.request).toHaveBeenCalledWith(payload); }); - }); - describe('legacy-send-provider', () => { - beforeEach(() => { - jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(true); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(false); + it('should pass request to provider and pass if provider returns "null', async () => { + const manager = new Web3RequestManager(); + const myProvider = { + request: jest.fn().mockImplementation(async _ => { + // Explicitly used for test case + // eslint-disable-next-line no-null/no-null + return null; + }), + } as any; + + jest.spyOn(manager, 'provider', 'get').mockReturnValue(myProvider); + + await expect(manager.send(request)).resolves.toBeNull(); + expect(myProvider.request).toHaveBeenCalledTimes(1); + expect(myProvider.request).toHaveBeenCalledWith(payload); }); + it('should pass request to provider and pass if provider returns "undefined', async () => { + const manager = new Web3RequestManager(); + const myProvider = { + request: jest.fn().mockImplementation(async _ => { + return undefined; + }), + } as any; + + jest.spyOn(manager, 'provider', 'get').mockReturnValue(myProvider); + + await expect(manager.send(request)).resolves.toBeNull(); + expect(myProvider.request).toHaveBeenCalledTimes(1); + expect(myProvider.request).toHaveBeenCalledWith(payload); + }); + }); + + describe('legacy-send-provider', () => { it('should pass request to provider and resolve if provider resolves it', async () => { const manager = new Web3RequestManager(); const myProvider = { @@ -540,13 +552,6 @@ describe('Web3RequestManager', () => { }); describe('legacy-send-async-provider', () => { - beforeEach(() => { - jest.spyOn(utils, 'isWeb3Provider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacyRequestProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendProvider').mockReturnValue(false); - jest.spyOn(utils, 'isLegacySendAsyncProvider').mockReturnValue(true); - }); - it('should pass request to provider and resolve if provider resolves it', async () => { const manager = new Web3RequestManager(); const myProvider = {