diff --git a/packages/web3-core/src/utils.ts b/packages/web3-core/src/utils.ts index 324f32dc4c5..e63b6c981fb 100644 --- a/packages/web3-core/src/utils.ts +++ b/packages/web3-core/src/utils.ts @@ -41,7 +41,7 @@ export const isLegacySendAsyncProvider = ( export const isSupportedProvider = ( provider: SupportedProviders, -): boolean => +): provider is SupportedProviders => Web3BaseProvider.isWeb3Provider(provider) || isLegacyRequestProvider(provider) || isLegacySendAsyncProvider(provider) || diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts index d23a4e9fa54..e52855b7d9d 100644 --- a/packages/web3-core/src/web3_context.ts +++ b/packages/web3-core/src/web3_context.ts @@ -21,7 +21,7 @@ import { Web3BaseWalletAccount, Web3AccountProvider, } from 'web3-common'; -import { HexString } from 'web3-utils'; +import { HexString, isNullish } from 'web3-utils'; import { SupportedProviders } from './types'; import { isSupportedProvider } from './utils'; // eslint-disable-next-line import/no-cycle @@ -41,7 +41,7 @@ export type Web3ContextObject< } = any, > = { config: Web3ConfigOptions; - provider: SupportedProviders; + provider?: SupportedProviders | string; requestManager: Web3RequestManager; subscriptionManager?: Web3SubscriptionManager | undefined; registeredSubscriptions?: RegisteredSubs; @@ -59,7 +59,7 @@ export type Web3ContextInitOptions< } = any, > = { config?: Partial; - provider: SupportedProviders | string; + provider?: SupportedProviders | string; requestManager?: Web3RequestManager; subscriptionManager?: Web3SubscriptionManager | undefined; registeredSubscriptions?: RegisteredSubs; @@ -99,17 +99,24 @@ export class Web3Context< public constructor( providerOrContext?: + | string | SupportedProviders - | Web3ContextInitOptions - | string, + | Web3ContextInitOptions, ) { super(); + + // If "providerOrContext" is provided as "string" or an objects matching "SupportedProviders" interface if ( - typeof providerOrContext === 'string' || + isNullish(providerOrContext) || + (typeof providerOrContext === 'string' && providerOrContext.trim() !== '') || isSupportedProvider(providerOrContext as SupportedProviders) ) { this._requestManager = new Web3RequestManager( - providerOrContext as SupportedProviders, + providerOrContext as undefined | string | SupportedProviders, + ); + this._subscriptionManager = new Web3SubscriptionManager( + this._requestManager, + {} as RegisteredSubs, ); return; @@ -123,7 +130,7 @@ export class Web3Context< registeredSubscriptions, accountProvider, wallet, - } = providerOrContext as Partial>; + } = providerOrContext as Web3ContextInitOptions; this.setConfig(config ?? {}); @@ -229,19 +236,19 @@ export class Web3Context< }); } - public get provider(): SupportedProviders { + public get provider(): SupportedProviders | string | undefined { return this.requestManager.provider; } - public set provider(provider: SupportedProviders | string) { + public set provider(provider: SupportedProviders | string | undefined) { this.requestManager.setProvider(provider); } - public get currentProvider(): SupportedProviders { + public get currentProvider(): SupportedProviders | string | undefined { return this.requestManager.provider; } - public set currentProvider(provider: SupportedProviders | string) { + public set currentProvider(provider: SupportedProviders | string | undefined) { this.requestManager.setProvider(provider); } @@ -250,7 +257,7 @@ export class Web3Context< return Web3Context.givenProvider; } - public setProvider(provider: SupportedProviders) { + public setProvider(provider?: SupportedProviders | string) { this.provider = provider; } diff --git a/packages/web3-core/src/web3_request_manager.ts b/packages/web3-core/src/web3_request_manager.ts index fc0b356f000..7e702d4f523 100644 --- a/packages/web3-core/src/web3_request_manager.ts +++ b/packages/web3-core/src/web3_request_manager.ts @@ -31,7 +31,6 @@ import { Web3APIRequest, Web3APIReturnType, Web3APISpec, - Web3BaseProvider, Web3EventEmitter, } from 'web3-common'; import HttpProvider from 'web3-providers-http'; @@ -60,14 +59,14 @@ const availableProviders = { export class Web3RequestManager< API extends Web3APISpec = EthExecutionAPI, > extends Web3EventEmitter<{ - [key in Web3RequestManagerEvent]: SupportedProviders; + [key in Web3RequestManagerEvent]: SupportedProviders | undefined; }> { - private _provider!: SupportedProviders; + private _provider?: SupportedProviders; public constructor(provider?: SupportedProviders | string, net?: Socket) { super(); - if (provider) { + if (!isNullish(provider)) { this.setProvider(provider, net); } } @@ -77,10 +76,6 @@ export class Web3RequestManager< } public get provider() { - if (!this._provider) { - throw new ProviderError('Provider not available'); - } - return this._provider; } @@ -89,8 +84,8 @@ export class Web3RequestManager< return availableProviders; } - public setProvider(provider: SupportedProviders | string, net?: Socket) { - let newProvider!: Web3BaseProvider; + public setProvider(provider?: SupportedProviders | string, net?: Socket) { + let newProvider: SupportedProviders | undefined; // autodetect provider if (provider && typeof provider === 'string' && this.providers) { @@ -106,12 +101,17 @@ export class Web3RequestManager< } else if (typeof net === 'object' && typeof net.connect === 'function') { newProvider = new this.providers.IpcProvider(provider, net); } else { - throw new ProviderError(`Can't autodetect provider for "${provider}'"`); + throw new ProviderError(`Can't autodetect provider for "${provider}"`); } + } else if (isNullish(provider)) { + // In case want to unset the provider + newProvider = undefined; + } else { + newProvider = provider as SupportedProviders; } this.emit(Web3RequestManagerEvent.BEFORE_PROVIDER_CHANGE, this._provider); - this._provider = newProvider ?? provider; + this._provider = newProvider; this.emit(Web3RequestManagerEvent.PROVIDER_CHANGED, this._provider); } @@ -142,6 +142,12 @@ export class Web3RequestManager< ): Promise> { const { provider } = this; + if (isNullish(provider)) { + throw new ProviderError( + 'Provider not available. Use `.setProvider` or `.provider=` to initialize the provider.', + ); + } + const payload = jsonRpc.isBatchRequest(request) ? jsonRpc.toBatchPayload(request) : jsonRpc.toPayload(request); diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index f9751e98d88..b91e7b0326b 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -126,6 +126,8 @@ export class Web3SubscriptionManager< } public supportsSubscriptions(): boolean { - return isSupportSubscriptions(this.requestManager.provider); + return isNullish(this.requestManager.provider) + ? false + : isSupportSubscriptions(this.requestManager.provider); } } diff --git a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap index a492c52aeb1..01bf48da89a 100644 --- a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap +++ b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap @@ -33,11 +33,14 @@ Object { "IpcProvider": [Function], "WebsocketProvider": [Function], }, - "registeredSubscriptions": undefined, + "registeredSubscriptions": Object {}, "requestManager": Web3RequestManager { "_emitter": EventEmitter { - "_events": Object {}, - "_eventsCount": 0, + "_events": Object { + "BEFORE_PROVIDER_CHANGE": [Function], + "PROVIDER_CHANGED": [Function], + }, + "_eventsCount": 2, "_maxListeners": undefined, Symbol(kCapture): false, }, @@ -46,7 +49,25 @@ Object { "httpProviderOptions": undefined, }, }, - "subscriptionManager": undefined, + "subscriptionManager": Web3SubscriptionManager { + "_subscriptions": Map {}, + "registeredSubscriptions": Object {}, + "requestManager": Web3RequestManager { + "_emitter": EventEmitter { + "_events": Object { + "BEFORE_PROVIDER_CHANGE": [Function], + "PROVIDER_CHANGED": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + Symbol(kCapture): false, + }, + "_provider": HttpProvider { + "clientUrl": "http://test/abc", + "httpProviderOptions": undefined, + }, + }, + }, "wallet": undefined, } `; 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 3823d1b8cb0..15f2cdc59e5 100644 --- a/packages/web3-core/test/unit/web3_request_manager.test.ts +++ b/packages/web3-core/test/unit/web3_request_manager.test.ts @@ -233,7 +233,7 @@ describe('Web3RequestManager', () => { const manager = new Web3RequestManager(); expect(() => manager.setProvider(providerString)).toThrow( - `Can't autodetect provider for "pc://mydomain.com'"`, + `Can't autodetect provider for "pc://mydomain.com"`, ); }); }); diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 7caae679df9..cca34153531 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -175,7 +175,7 @@ export class Contract super({ ...context, // Pass an empty string to avoid type issue. Error will be thrown from underlying validation - provider: options?.provider ?? context?.provider ?? Contract.givenProvider ?? '', + provider: options?.provider ?? context?.provider ?? Contract.givenProvider, registeredSubscriptions: { logs: LogsSubscription, newHeads: NewHeadsSubscription, diff --git a/packages/web3-eth-contract/src/types.ts b/packages/web3-eth-contract/src/types.ts index 663adb3785e..f02769330ec 100644 --- a/packages/web3-eth-contract/src/types.ts +++ b/packages/web3-eth-contract/src/types.ts @@ -61,7 +61,7 @@ export interface ContractInitOptions { readonly from?: Address; readonly data?: Bytes; readonly gasLimit?: Uint; - readonly provider: SupportedProviders | string; + readonly provider?: SupportedProviders | string; } export type TransactionReceipt = ReceiptInfo; diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index 9310506c991..0bb59953c82 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -18,7 +18,12 @@ along with web3.js. If not, see . // Disabling because returnTypes must be last param to match 1.x params /* eslint-disable default-param-last */ import { DataFormat, DEFAULT_RETURN_FORMAT } from 'web3-common'; -import { SupportedProviders, Web3Context, Web3ContextInitOptions } from 'web3-core'; +import { + isSupportedProvider, + SupportedProviders, + Web3Context, + Web3ContextInitOptions, +} from 'web3-core'; import { Address, Bytes, @@ -53,28 +58,40 @@ type RegisteredSubscription = { syncing: typeof SyncingSubscription; }; +const registeredSubscriptions = { + logs: LogsSubscription, + newPendingTransactions: NewPendingTransactionsSubscription, + newHeads: NewHeadsSubscription, + syncing: SyncingSubscription, + pendingTransactions: NewPendingTransactionsSubscription, // the same as newPendingTransactions. just for support API like in version 1.x + newBlockHeaders: NewHeadsSubscription, // the same as newHeads. just for support API like in version 1.x +}; + export class Web3Eth extends Web3Context { public constructor( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - providerOrContext: SupportedProviders | Web3ContextInitOptions | string, + providerOrContext?: SupportedProviders | Web3ContextInitOptions | string, ) { - super( - typeof providerOrContext === 'object' && - (providerOrContext as Web3ContextInitOptions).provider - ? providerOrContext - : { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - provider: providerOrContext as SupportedProviders, - registeredSubscriptions: { - logs: LogsSubscription, - newPendingTransactions: NewPendingTransactionsSubscription, - newHeads: NewHeadsSubscription, - syncing: SyncingSubscription, - pendingTransactions: NewPendingTransactionsSubscription, // the same as newPendingTransactions. just for support API like in version 1.x - newBlockHeaders: NewHeadsSubscription, // the same as newHeads. just for support API like in version 1.x - }, - }, - ); + if ( + typeof providerOrContext === 'string' || + isSupportedProvider(providerOrContext as SupportedProviders) + ) { + super({ + provider: providerOrContext as SupportedProviders, + registeredSubscriptions, + }); + + return; + } + + if ((providerOrContext as Web3ContextInitOptions).registeredSubscriptions) { + super(providerOrContext as Web3ContextInitOptions); + return; + } + + super({ + ...(providerOrContext as Web3ContextInitOptions), + registeredSubscriptions, + }); } public async getProtocolVersion() { return rpcMethods.getProtocolVersion(this.requestManager); diff --git a/packages/web3-validator/src/validation/numbers.ts b/packages/web3-validator/src/validation/numbers.ts index c87a1b872b4..6edebb456fc 100644 --- a/packages/web3-validator/src/validation/numbers.ts +++ b/packages/web3-validator/src/validation/numbers.ts @@ -38,6 +38,7 @@ export const isUInt = ( ) { return false; } + let size!: number; if (options?.abiType) { diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index 4405091cf14..3385657731d 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -61,10 +61,11 @@ export class Web3 extends Web3Context { Personal, }; + public utils: typeof utils; + public eth: Web3Eth & { Iban: typeof Iban; ens: ENS; - utils: typeof utils; net: Net; personal: Personal; Contract: typeof Contract & { @@ -93,7 +94,7 @@ export class Web3 extends Web3Context { }; }; - public constructor(provider: SupportedProviders | string) { + public constructor(provider?: SupportedProviders | string) { const accountProvider = { create, privateKeyToAccount, @@ -108,6 +109,8 @@ export class Web3 extends Web3Context { super({ provider, wallet, accountProvider }); + this.utils = utils; + // Have to use local alias to initiate contract context // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; @@ -146,8 +149,6 @@ export class Web3 extends Web3Context { net: self.use(Net), personal: self.use(Personal), - utils, - // Contract helper and module Contract: ContractBuilder, diff --git a/packages/web3/test/integration/main_web3.test.ts b/packages/web3/test/integration/main_web3.test.ts index 18946604503..9d44aa7aed6 100644 --- a/packages/web3/test/integration/main_web3.test.ts +++ b/packages/web3/test/integration/main_web3.test.ts @@ -75,15 +75,45 @@ describe('Web3 instance', () => { if (getSystemTestProvider().startsWith('ws')) { // make sure we try to close the connection after it is established if ( - web3.provider && + web3?.provider && (web3.provider as unknown as Web3BaseProvider).getStatus() === 'connecting' ) { await waitForOpenConnection(web3, currentAttempt); } - (web3.provider as unknown as Web3BaseProvider).disconnect(1000, ''); + + if (web3?.provider) { + (web3.provider as unknown as Web3BaseProvider).disconnect(1000, ''); + } } }); + it('should be able to create web3 object without provider', () => { + expect(() => new Web3()).not.toThrow(); + }); + + it('should be able use "utils" without provider', () => { + web3 = new Web3(); + + expect(web3.utils.hexToNumber('0x5')).toBe(5); + }); + + it('should be able use "abi" without provider', () => { + web3 = new Web3(); + const validData = validEncodeParametersData[0]; + + const encodedParameters = web3.eth.abi.encodeParameters( + validData.input[0], + validData.input[1], + ); + expect(encodedParameters).toEqual(validData.output); + }); + + it('should throw error when we make a request when provider not available', async () => { + web3 = new Web3(); + + await expect(web3.eth.getChainId()).rejects.toThrow('Provider not available'); + }); + describeIf(getSystemTestProvider().startsWith('http'))( 'Create Web3 class instance with http string providers', () => { @@ -97,7 +127,7 @@ describe('Web3 instance', () => { ? process.env.INFURA_GOERLI_HTTP.toString().includes('http') : false, )('should create instance with string of external http provider', async () => { - web3 = new Web3(process.env.INFURA_GOERLI_HTTP!); + web3 = new Web3(process.env.INFURA_GOERLI_HTTP); // eslint-disable-next-line jest/no-standalone-expect expect(web3).toBeInstanceOf(Web3); }); @@ -132,14 +162,14 @@ describe('Web3 instance', () => { ? process.env.INFURA_GOERLI_WS.toString().includes('ws') : false, )('should create instance with string of external ws provider', async () => { - web3 = new Web3(process.env.INFURA_GOERLI_WS!); + web3 = new Web3(process.env.INFURA_GOERLI_WS); // eslint-disable-next-line jest/no-standalone-expect expect(web3).toBeInstanceOf(Web3); }); }, ); describe('Web3 providers', () => { - it('should set the provider', async () => { + it('should set the provider with `.provider=`', async () => { web3 = new Web3('http://dummy.com'); web3.provider = clientUrl; @@ -151,7 +181,7 @@ describe('Web3 instance', () => { expect(response).toEqual(expect.any(BigInt)); }); - it('setProvider', async () => { + it('should set the provider with `.setProvider`', async () => { let newProvider: Web3BaseProvider; web3 = new Web3('http://dummy.com'); if (clientUrl.startsWith('http')) { @@ -165,6 +195,38 @@ describe('Web3 instance', () => { expect(web3.provider).toBe(newProvider); }); + it('should set the provider with `.setProvider` of empty initialized object', async () => { + web3 = new Web3(); + + web3.setProvider(getSystemTestProvider()); + + await expect(web3.eth.getChainId()).resolves.toBeDefined(); + }); + + it('should set the provider with `.provider=` of empty initialized object', async () => { + web3 = new Web3(); + + web3.provider = getSystemTestProvider(); + + await expect(web3.eth.getChainId()).resolves.toBeDefined(); + }); + + it('should unset the provider with `.setProvider`', async () => { + web3 = new Web3(getSystemTestProvider()); + await expect(web3.eth.getChainId()).resolves.toBeDefined(); + + web3.setProvider(undefined); + await expect(web3.eth.getChainId()).rejects.toThrow('Provider not available'); + }); + + it('should unset the provider with `.provider=`', async () => { + web3 = new Web3(getSystemTestProvider()); + await expect(web3.eth.getChainId()).resolves.toBeDefined(); + + web3.provider = undefined; + await expect(web3.eth.getChainId()).rejects.toThrow('Provider not available'); + }); + it('providers', async () => { const res = Web3.providers;