diff --git a/packages/web3-rpc-providers/CHANGELOG.md b/packages/web3-rpc-providers/CHANGELOG.md index 52a8508d082..d07ba9124a4 100644 --- a/packages/web3-rpc-providers/CHANGELOG.md +++ b/packages/web3-rpc-providers/CHANGELOG.md @@ -55,4 +55,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Updated rate limit error of QuickNode provider for HTTP transport \ No newline at end of file +- Updated rate limit error of QuickNode provider for HTTP transport +- Added optional `HttpProviderOptions | SocketOptions` in `Web3ExternalProvider` and `QuickNodeProvider` for provider configs \ No newline at end of file diff --git a/packages/web3-rpc-providers/src/errors.ts b/packages/web3-rpc-providers/src/errors.ts index 54ad3094596..d698fbb66ab 100644 --- a/packages/web3-rpc-providers/src/errors.ts +++ b/packages/web3-rpc-providers/src/errors.ts @@ -15,6 +15,8 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +/* eslint-disable max-classes-per-file */ + import { BaseWeb3Error } from 'web3-errors'; const ERR_QUICK_NODE_RATE_LIMIT = 1300; @@ -24,4 +26,14 @@ export class QuickNodeRateLimitError extends BaseWeb3Error { public constructor(error?: Error) { super(`You've reach the rate limit of free RPC calls from our Partner Quick Nodes. There are two options you can either create a paid Quick Nodes account and get 20% off for 2 months using WEB3JS referral code, or use Free public RPC endpoint.`, error); } -} \ No newline at end of file +} + +const ERR_PROVIDER_CONFIG_OPTIONS = 1301; +export class ProviderConfigOptionsError extends BaseWeb3Error { + public code = ERR_PROVIDER_CONFIG_OPTIONS; + + public constructor(msg: string) { + super(`Invalid provider config options given for ${msg}`); + } +} +/* eslint-enable max-classes-per-file */ \ No newline at end of file diff --git a/packages/web3-rpc-providers/src/types.ts b/packages/web3-rpc-providers/src/types.ts index 9da4c1dbfe7..e23714977c1 100644 --- a/packages/web3-rpc-providers/src/types.ts +++ b/packages/web3-rpc-providers/src/types.ts @@ -15,6 +15,9 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import {ClientOptions, ClientRequestArgs} from "web3-providers-ws"; +import { ReconnectOptions } from 'web3-utils'; + export enum Transport { HTTPS = "https", WebSocket = "wss" @@ -41,4 +44,10 @@ export enum Network { BNB_MAINNET = "bnb_mainnet", BNB_TESTNET = "bnb_testnet" +}; + +// Combining the ws types +export type SocketOptions = { + socketOptions?: ClientOptions | ClientRequestArgs; + reconnectOptions?: Partial; }; \ No newline at end of file diff --git a/packages/web3-rpc-providers/src/web3_provider.ts b/packages/web3-rpc-providers/src/web3_provider.ts index dbd50cf9e56..498dec60433 100644 --- a/packages/web3-rpc-providers/src/web3_provider.ts +++ b/packages/web3-rpc-providers/src/web3_provider.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 HttpProvider from "web3-providers-http"; +import HttpProvider, { HttpProviderOptions } from "web3-providers-http"; import WebSocketProvider from "web3-providers-ws"; import { EthExecutionAPI, JsonRpcResult, ProviderConnectInfo, ProviderMessage, @@ -27,7 +27,8 @@ import { JsonRpcResponseWithResult, } from "web3-types"; import { Eip1193Provider } from "web3-utils"; -import { Transport, Network } from "./types.js"; +import { Transport, Network, SocketOptions } from "./types.js"; +import { ProviderConfigOptionsError } from "./errors.js"; /* This class can be used to create new providers only when there is custom logic required in each Request method like @@ -50,16 +51,36 @@ export abstract class Web3ExternalProvider< network: Network, transport: Transport, token: string, - host: string) { + host: string, + providerConfigOptions?: HttpProviderOptions | SocketOptions) { super(); + if(providerConfigOptions!== undefined && + transport === Transport.HTTPS && + !('providerOptions' in providerConfigOptions)){ + + throw new ProviderConfigOptionsError("HTTP Provider"); + } + else if(providerConfigOptions!== undefined && + transport === Transport.WebSocket && + !( 'socketOptions' in providerConfigOptions || + 'reconnectOptions' in providerConfigOptions + )){ + throw new ProviderConfigOptionsError("Websocket Provider"); + } + this.transport = transport; if (transport === Transport.HTTPS) { - this.provider = new HttpProvider(this.getRPCURL(network, transport, token, host)); + this.provider = new HttpProvider( + this.getRPCURL(network, transport, token, host), + providerConfigOptions as HttpProviderOptions); } else if (transport === Transport.WebSocket) { - this.provider = new WebSocketProvider(this.getRPCURL(network, transport, token, host)); + this.provider = new WebSocketProvider( + this.getRPCURL(network, transport, token, host), + (providerConfigOptions as SocketOptions)?.socketOptions, + (providerConfigOptions as SocketOptions)?.reconnectOptions); } } @@ -133,4 +154,5 @@ export abstract class Web3ExternalProvider< // eslint-disable-next-line @typescript-eslint/no-unsafe-argument this.provider.removeListener(_type as any, _listener as any); } -} \ No newline at end of file +} + diff --git a/packages/web3-rpc-providers/src/web3_provider_quicknode.ts b/packages/web3-rpc-providers/src/web3_provider_quicknode.ts index 4444110afdd..459cf5d5d38 100644 --- a/packages/web3-rpc-providers/src/web3_provider_quicknode.ts +++ b/packages/web3-rpc-providers/src/web3_provider_quicknode.ts @@ -17,7 +17,8 @@ along with web3.js. If not, see . import { EthExecutionAPI, JsonRpcResponseWithResult, Web3APIMethod, Web3APIPayload, Web3APIReturnType, Web3APISpec } from "web3-types"; import { ResponseError } from "web3-errors"; -import { Transport, Network } from "./types.js"; +import { HttpProviderOptions } from "web3-providers-http"; +import { Transport, Network, SocketOptions } from "./types.js"; import { Web3ExternalProvider } from "./web3_provider.js"; import { QuickNodeRateLimitError } from "./errors.js"; @@ -27,13 +28,10 @@ export class QuickNodeProvider< API extends Web3APISpec = EthExecutionAPI, > extends Web3ExternalProvider { - public constructor( - network: Network = Network.ETH_MAINNET, - transport: Transport = Transport.HTTPS, - token = "", - host = "") { + // eslint-disable-next-line default-param-last + public constructor( network: Network = Network.ETH_MAINNET, transport: Transport = Transport.HTTPS, token = "", host = "", providerConfigOptions?: HttpProviderOptions | SocketOptions) { - super(network, transport, token, host); + super(network, transport, token, host, providerConfigOptions); } diff --git a/packages/web3-rpc-providers/test/unit/constructor.test.ts b/packages/web3-rpc-providers/test/unit/constructor.test.ts index 0daedb8c77d..16133129d55 100644 --- a/packages/web3-rpc-providers/test/unit/constructor.test.ts +++ b/packages/web3-rpc-providers/test/unit/constructor.test.ts @@ -16,12 +16,13 @@ along with web3.js. If not, see . */ -import HttpProvider from 'web3-providers-http'; +import HttpProvider, { HttpProviderOptions } from 'web3-providers-http'; import WebSocketProvider from 'web3-providers-ws'; import WebSocket from 'isomorphic-ws'; import { Web3ExternalProvider } from '../../src/web3_provider'; -import { Network, Transport } from '../../src/types'; +import { Network, SocketOptions, Transport } from '../../src/types'; +import { ProviderConfigOptionsError } from '../../src/errors'; // Mock implementation so ws doesnt have openhandle after test exits as it attempts to connects at start jest.mock('isomorphic-ws', () => { @@ -60,11 +61,11 @@ jest.mock('isomorphic-ws', () => { }); class MockWeb3ExternalProviderA extends Web3ExternalProvider { - public constructor(network: Network, transport: Transport, token: string){ - super(network, transport, token, ""); + public constructor(network: Network, transport: Transport, token: string, host?: string, providerConfigOptions?: HttpProviderOptions | SocketOptions) { + super(network, transport, token, host ?? "", providerConfigOptions); } // eslint-disable-next-line class-methods-use-this - public getRPCURL(_network: Network, _transport: Transport, _token: string, _host=""): string { + public getRPCURL(_network: Network, _transport: Transport, _token: string, _host = ""): string { let transport = ""; if (_transport === Transport.HTTPS) transport = "http://"; @@ -76,10 +77,12 @@ class MockWeb3ExternalProviderA extends Web3ExternalProvider { } describe('Web3ExternalProvider', () => { + const network: Network = Network.ETH_MAINNET; + const transport: Transport = Transport.HTTPS; + const token = 'test-token'; + const host = 'test-host'; + it('should initialize the provider correctly', () => { - const network: Network = Network.ETH_MAINNET; - const transport: Transport = Transport.HTTPS; - const token = 'your-token'; const provider = new MockWeb3ExternalProviderA(network, transport, token); @@ -87,12 +90,72 @@ describe('Web3ExternalProvider', () => { }); it('should initialize the provider with WebSocketProvider for WebSocket transport', () => { - const network: Network = Network.ETH_MAINNET; - const transport: Transport = Transport.WebSocket; - const token = 'your-token'; + const transport1: Transport = Transport.WebSocket; - const provider = new MockWeb3ExternalProviderA(network, transport, token); + const provider = new MockWeb3ExternalProviderA(network, transport1, token); expect(provider.provider).toBeInstanceOf(WebSocketProvider); }); + it('should throw ProviderConfigOptionsError for HTTP provider with missing providerOptions', () => { + const providerConfigOptions: HttpProviderOptions | SocketOptions = { /* missing providerOptions */ }; + expect(() => new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError); + }); + + it('should throw ProviderConfigOptionsError for HTTP provider with WS providerOptions', () => { + const providerConfigOptions: SocketOptions = { + socketOptions: { /* options */ }, + reconnectOptions: { /* options */ }, + }; + expect(() => new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError); + }); + + it('should throw ProviderConfigOptionsError for WebSocket provider with missing socketOptions and reconnectOptions', () => { + const providerConfigOptions: HttpProviderOptions | SocketOptions = { /* missing socketOptions and reconnectOptions */ }; + expect(() => new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError); + }); + + it('should throw ProviderConfigOptionsError for WebSocket provider with HTTP options', () => { + const providerConfigOptions: HttpProviderOptions = { providerOptions: { /* options */ } }; + expect(() => new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions)).toThrow(ProviderConfigOptionsError); + }); + + it('should create provider instance and not throw ProviderConfigOptionsError for WebSocket provider with missing reconnectOptions', () => { + const providerConfigOptions: SocketOptions = { + socketOptions: { /* options */ }, + }; + + // Create an instance of the MockWeb3ExternalProviderA + const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions); + + // Expect that the provider is created successfully + expect(provider).toBeInstanceOf(MockWeb3ExternalProviderA); + }); + + it('should create provider instance and not throw ProviderConfigOptionsError for WebSocket provider with missing socketOptions', () => { + const providerConfigOptions: SocketOptions = { + reconnectOptions: { /* options */ }, + }; + + // Create an instance of the MockWeb3ExternalProviderA + const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions); + + // Expect that the provider is created successfully + expect(provider).toBeInstanceOf(MockWeb3ExternalProviderA); + }); + + it('should create an HttpProvider with providerOptions', () => { + const providerConfigOptions: HttpProviderOptions = { providerOptions: { /* options */ } }; + const provider = new MockWeb3ExternalProviderA(network, transport, token, host, providerConfigOptions); + expect(provider.provider).toBeInstanceOf(HttpProvider); + }); + + it('should create a WebSocketProvider with socketOptions and reconnectOptions', () => { + const providerConfigOptions: SocketOptions = { + socketOptions: { /* options */ }, + reconnectOptions: { /* options */ }, + }; + const provider = new MockWeb3ExternalProviderA(network, Transport.WebSocket, token, host, providerConfigOptions); + expect(provider.provider).toBeInstanceOf(WebSocketProvider); + }); }); +