/* 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 . */ // eslint-disable-next-line max-classes-per-file import { ExistingPluginNamespaceError } from 'web3-errors'; import { EthExecutionAPI, HexString, Numbers, SupportedProviders, Transaction, Web3AccountProvider, Web3APISpec, Web3BaseProvider, Web3BaseWallet, Web3BaseWalletAccount, } from 'web3-types'; 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, 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'; import { Web3RequestManager } from './web3_request_manager.js'; import { Web3SubscriptionConstructor } from './web3_subscriptions.js'; import { Web3SubscriptionManager } from './web3_subscription_manager.js'; // To avoid circular dependencies, we need to export type from here. export type Web3ContextObject< API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any } = any, > = { config: Web3ConfigOptions; provider?: SupportedProviders | string; requestManager: Web3RequestManager; subscriptionManager?: Web3SubscriptionManager | undefined; registeredSubscriptions?: RegisteredSubs; providers: typeof Web3RequestManager.providers; accountProvider?: Web3AccountProvider; wallet?: Web3BaseWallet; }; export type Web3ContextInitOptions< API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any } = any, > = { config?: Partial; provider?: SupportedProviders | string; requestManager?: Web3RequestManager; subscriptionManager?: Web3SubscriptionManager | undefined; registeredSubscriptions?: RegisteredSubs; accountProvider?: Web3AccountProvider; wallet?: Web3BaseWallet; requestManagerMiddleware?: RequestManagerMiddleware; }; // eslint-disable-next-line no-use-before-define export type Web3ContextConstructor = new ( ...args: [...extras: T2, context: Web3ContextObject] ) => T; // To avoid circular dependencies, we need to export type from here. export type Web3ContextFactory< // eslint-disable-next-line no-use-before-define T extends Web3Context, T2 extends unknown[], > = Web3ContextConstructor & { fromContextObject(this: Web3ContextConstructor, contextObject: Web3ContextObject): T; }; export class Web3Context< API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any } = any, > extends Web3Config { public static readonly providers = Web3RequestManager.providers; public static givenProvider?: SupportedProviders; public readonly providers = Web3RequestManager.providers; protected _requestManager: Web3RequestManager; protected _subscriptionManager: Web3SubscriptionManager; protected _accountProvider?: Web3AccountProvider; protected _wallet?: Web3BaseWallet; public constructor( providerOrContext?: | string | SupportedProviders | Web3ContextInitOptions, ) { super(); // If "providerOrContext" is provided as "string" or an objects matching "SupportedProviders" interface if ( isNullish(providerOrContext) || (typeof providerOrContext === 'string' && providerOrContext.trim() !== '') || isSupportedProvider(providerOrContext as SupportedProviders) ) { this._requestManager = new Web3RequestManager( providerOrContext as undefined | string | SupportedProviders, ); this._subscriptionManager = new Web3SubscriptionManager( this._requestManager, {} as RegisteredSubs, ); return; } const { config, provider, requestManager, subscriptionManager, registeredSubscriptions, accountProvider, wallet, requestManagerMiddleware, } = providerOrContext as Web3ContextInitOptions; this.setConfig(config ?? {}); this._requestManager = requestManager ?? new Web3RequestManager( provider, config?.enableExperimentalFeatures?.useSubscriptionWhenCheckingBlockTimeout, requestManagerMiddleware, ); if (subscriptionManager) { this._subscriptionManager = subscriptionManager; } else { this._subscriptionManager = new Web3SubscriptionManager( this.requestManager, registeredSubscriptions ?? ({} as RegisteredSubs), ); } if (accountProvider) { this._accountProvider = accountProvider; } if (wallet) { this._wallet = wallet; } } public get requestManager() { return this._requestManager; } /** * Will return the current subscriptionManager ({@link Web3SubscriptionManager}) */ public get subscriptionManager() { return this._subscriptionManager; } public get wallet() { return this._wallet; } public get accountProvider() { return this._accountProvider; } // eslint-disable-next-line @typescript-eslint/no-explicit-any public static fromContextObject( this: Web3ContextConstructor, ...args: [Web3ContextObject, ...T3] ) { return new this(...(args.reverse() as [...T3, Web3ContextObject])); } public getContextObject(): Web3ContextObject { return { config: this.config, provider: this.provider, requestManager: this.requestManager, subscriptionManager: this.subscriptionManager, registeredSubscriptions: this.subscriptionManager?.registeredSubscriptions, providers: this.providers, wallet: this.wallet, accountProvider: this.accountProvider, }; } /** * Use to create new object of any type extended by `Web3Context` * and link it to current context. This can be used to initiate a global context object * and then use it to create new objects of any type extended by `Web3Context`. */ public use( ContextRef: Web3ContextConstructor, ...args: [...T2] ) { const newContextChild: T = new ContextRef( ...([...args, this.getContextObject()] as unknown as [...T2, Web3ContextObject]), ); this.on(Web3ConfigEvent.CONFIG_CHANGE, event => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment newContextChild.setConfig({ [event.name]: event.newValue }); }); // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' this[ContextRef.name] = newContextChild; return newContextChild; } /** * Link current context to another context. */ public link(parentContext: T) { this.setConfig(parentContext.config); this._requestManager = parentContext.requestManager; this.provider = parentContext.provider; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._subscriptionManager = parentContext.subscriptionManager; this._wallet = parentContext.wallet; this._accountProvider = parentContext._accountProvider; parentContext.on(Web3ConfigEvent.CONFIG_CHANGE, event => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.setConfig({ [event.name]: event.newValue }); }); } // eslint-disable-next-line no-use-before-define public registerPlugin(plugin: Web3PluginBase) { // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' if (this[plugin.pluginNamespace] !== undefined) throw new ExistingPluginNamespaceError(plugin.pluginNamespace); const _pluginObject = { [plugin.pluginNamespace]: plugin, }; _pluginObject[plugin.pluginNamespace].link(this); Object.assign(this, _pluginObject); } /** * Will return the current provider. * * @returns Returns the current provider * @example * ```ts * const web3 = new Web3Context("http://localhost:8545"); * console.log(web3.provider); * > HttpProvider { * clientUrl: 'http://localhost:8545', * httpProviderOptions: undefined * } * ``` */ public get provider(): Web3BaseProvider | undefined { return this.currentProvider; } /** * Will set the current provider. * * @param provider - The provider to set * * Accepted providers are of type {@link SupportedProviders} * @example * ```ts * const web3Context = new web3ContextContext("http://localhost:8545"); * web3Context.provider = "ws://localhost:8545"; * console.log(web3Context.provider); * > WebSocketProvider { * _eventEmitter: EventEmitter { * _events: [Object: null prototype] {}, * _eventsCount: 0, * ... * } * ``` */ public set provider(provider: SupportedProviders | string | undefined) { this.requestManager.setProvider(provider); } /** * Will return the current provider. (The same as `provider`) * * @returns Returns the current provider * @example * ```ts * const web3Context = new Web3Context("http://localhost:8545"); * console.log(web3Context.provider); * > HttpProvider { * clientUrl: 'http://localhost:8545', * httpProviderOptions: undefined * } * ``` */ public get currentProvider(): Web3BaseProvider | undefined { return this.requestManager.provider as Web3BaseProvider; } /** * Will set the current provider. (The same as `provider`) * * @param provider - {@link SupportedProviders} The provider to set * * @example * ```ts * const web3Context = new Web3Context("http://localhost:8545"); * web3Context.currentProvider = "ws://localhost:8545"; * console.log(web3Context.provider); * > WebSocketProvider { * _eventEmitter: EventEmitter { * _events: [Object: null prototype] {}, * _eventsCount: 0, * ... * } * ``` */ public set currentProvider(provider: SupportedProviders | string | undefined) { this.requestManager.setProvider(provider); } /** * Will return the givenProvider if available. * * When using web3.js in an Ethereum compatible browser, it will set with the current native provider by that browser. Will return the given provider by the (browser) environment, otherwise `undefined`. */ // eslint-disable-next-line class-methods-use-this public get givenProvider() { return Web3Context.givenProvider; } /** * Will set the provider. * * @param provider - {@link SupportedProviders} The provider to set * @returns Returns true if the provider was set */ public setProvider(provider?: SupportedProviders | string): boolean { this.provider = provider; return true; } public setRequestManagerMiddleware(requestManagerMiddleware: RequestManagerMiddleware) { this.requestManager.setMiddleware(requestManagerMiddleware); } /** * Will return the {@link Web3BatchRequest} constructor. */ public get BatchRequest(): new () => Web3BatchRequest { return Web3BatchRequest.bind( undefined, this._requestManager as unknown as Web3RequestManager, ); } /** * This method allows extending the web3 modules. * Note: This method is only for backward compatibility, and It is recommended to use Web3 v4 Plugin feature for extending web3.js functionality if you are developing something new. */ public extend(extendObj: ExtensionObject) { // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' if (extendObj.property && !this[extendObj.property]) // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' this[extendObj.property] = {}; extendObj.methods?.forEach(element => { const method = async (...givenParams: unknown[]) => this.requestManager.send({ method: element.call, params: givenParams, }); if (extendObj.property) // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this[extendObj.property][element.name] = method; // @ts-expect-error No index signature with a parameter of type 'string' was found on type 'Web3Context' else this[element.name] = method; }); return this; } } /** * Extend this class when creating a plugin that either doesn't require {@link EthExecutionAPI}, * or interacts with a RPC node that doesn't fully implement {@link EthExecutionAPI}. * * To add type support for RPC methods to the {@link Web3RequestManager}, * define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so: * * @example * ```ts * type CustomRpcApi = { * custom_rpc_method: () => string; * custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; * }; * * class CustomPlugin extends Web3PluginBase {...} * ``` */ export abstract class Web3PluginBase< API extends Web3APISpec = Web3APISpec, > extends Web3Context { public abstract pluginNamespace: string; // eslint-disable-next-line class-methods-use-this protected registerNewTransactionType>( type: Numbers, txClass: NewTxTypeClass, ): void { TransactionFactory.registerTransactionType(type, txClass); } } /** * Extend this class when creating a plugin that makes use of {@link EthExecutionAPI}, * or depends on other Web3 packages (such as `web3-eth-contract`) that depend on {@link EthExecutionAPI}. * * To add type support for RPC methods to the {@link Web3RequestManager} (in addition to {@link EthExecutionAPI}), * define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so: * * @example * ```ts * type CustomRpcApi = { * custom_rpc_method: () => string; * custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; * }; * * class CustomPlugin extends Web3PluginBase {...} * ``` */ export abstract class Web3EthPluginBase extends Web3PluginBase< API & EthExecutionAPI > {} // To avoid cycle dependency declare this type in this file export type TransactionBuilder = < ReturnType = Transaction, >(options: { transaction: Transaction; web3Context: Web3Context; privateKey?: HexString | Uint8Array; fillGasPrice?: boolean; }) => Promise;