This repository was archived by the owner on Mar 5, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Init Plugin Docs #5558
Merged
Merged
Init Plugin Docs #5558
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
7ff7cd7
Update web3.ts to have a named export instead of using default
spacesailor24 d3820e3
Init web3-plugin-example package
spacesailor24 a61db39
Update CHANGELOG and README
spacesailor24 40bbb33
Update packages/web3-plugin-example/test/unit/plugin.test.ts
spacesailor24 67c38d5
Remove unneeded npm scripts
spacesailor24 d99f46e
Remove webpack config
spacesailor24 1fbcaff
Update Web3 to use default and named exports
spacesailor24 5834b5c
Add peerDependencies
spacesailor24 806f93e
Remove use of ChainlinkPluginAPI. Define return type for getPrice
spacesailor24 915e7f9
Override link method for plugin
spacesailor24 273b609
Remove ChainlinkPlugin code
spacesailor24 6079d72
Init custom_rpc_methods plugin example
spacesailor24 f8b0152
Init contract_method_wrappers plugin example
spacesailor24 adab218
Update web3 import to remove import ts error
spacesailor24 bb41fb7
Fix typing compatibility when linking a contract to a context (#5416)
Muhammad-Altabba 44d5255
Move web3-plugin-example to tools/. Remove @ts-expect-errors. Move We…
spacesailor24 b7a2035
Update CHANGELOGs
spacesailor24 cacfb2d
Update packages/web3-types/CHANGELOG.md
spacesailor24 29de54d
Update CHANGELOG.md
spacesailor24 d9fd46a
Revert change to Web3 export
spacesailor24 3c04230
WIP plugin docs
spacesailor24 ac3a701
Update plugin_authors doc
spacesailor24 941e29c
Update plugin_users doc
spacesailor24 a342c79
Rewording
spacesailor24 5bf6123
Update packages/web3/CHANGELOG.md
spacesailor24 a15038d
Update CHANGELOG.md
spacesailor24 8013448
Update docs/docs/guides/web3_plugin_guide/plugin_authors.md
spacesailor24 d475cf8
Update docs/docs/guides/web3_plugin_guide/plugin_users.md
spacesailor24 90e1c05
Update docs/docs/guides/web3_plugin_guide/plugin_users.md
spacesailor24 bfde86d
Apply suggestions from code review
spacesailor24 e99b645
Update tree shaking guide to sidebar position 2
spacesailor24 403ad79
Update plugin authors sidebar position to 0
spacesailor24 91a1277
Merge branch 'wyatt/4.x/5526-plugin-docs' of github.com:ChainSafe/web…
spacesailor24 07ddb1c
Update docs/docs/guides/web3_plugin_guide/plugin_users.md
spacesailor24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+54.4 KB
docs/docs/guides/web3_plugin_guide/assets/custom_rpc_method_with_parameters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| --- | ||
| sidebar_position: 1 | ||
| sidebar_label: 'Web3 Plugins' | ||
| --- | ||
|
|
||
| # Web3.js Plugins Guide | ||
|
|
||
| In addition to the Web3.js standard libraries, plugins add specific functionality to the end user. This extra functionality could be wrappers around specific contracts, additional RPC method wrappers, or could even extend the logic of Web3.js methods. | ||
|
|
||
| ## Before Getting Started | ||
|
|
||
| ### Module Augmentation | ||
|
|
||
| In order to provide typing support for the registered plugin, the plugin user must [augment the module](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) they're registering the plugin with. In simpler terms, we must make TypeScript aware that we are modifying a module's (i.e. a package such as `web3` or `web3-eth`) interface with additional methods, properties, and/or classes. A good tutorial that further explains the topic can be found [here](https://www.digitalocean.com/community/tutorials/typescript-module-augmentation). | ||
|
|
||
| The `registerPlugin` method exists on the `Web3Context` class, so any class that `extends` `Web3Context` has the ability to add on the plugin's additional functionality to it's interface. Because of this, the burden of module augmentation falls on the plugin user as Web3.js and the plugin author are unaware of the module the end user is calling `registerPlugin` on. | ||
|
|
||
| #### Web3.js Example | ||
|
|
||
| The following is an example plugin that adds additional RPC method wrappers: | ||
|
|
||
| ```typescript | ||
| // custom_rpc_methods_plugin.ts | ||
| import { Web3PluginBase } from 'web3-core'; | ||
|
|
||
| type CustomRpcApi = { | ||
| custom_rpc_method: () => string; | ||
| custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; | ||
| }; | ||
|
|
||
| export class CustomRpcMethodsPlugin extends Web3PluginBase<CustomRpcApi> { | ||
| public pluginNamespace = 'customRpcMethods'; | ||
|
|
||
| public async customRpcMethod() { | ||
| return this.requestManager.send({ | ||
| method: 'custom_rpc_method', | ||
| params: [], | ||
| }); | ||
| } | ||
|
|
||
| public async customRpcMethodWithParameters(parameter1: string, parameter2: number) { | ||
| return this.requestManager.send({ | ||
| method: 'custom_rpc_method_with_parameters', | ||
| params: [parameter1, parameter2], | ||
| }); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| In the below example, the end user is registering the above `CustomRpcMethodsPlugin` with an instance of `Web3Context`: | ||
|
|
||
| ```typescript | ||
| // registering_a_plugin.ts | ||
| import { Web3Context } from 'web3-core'; | ||
|
|
||
| import { CustomRpcMethodsPlugin } from './custom_rpc_methods_plugin'; | ||
|
|
||
| declare module 'web3-core' { | ||
| interface Web3Context { | ||
| customRpcMethods: CustomRpcMethodsPlugin; | ||
| } | ||
| } | ||
|
|
||
| const web3Context = new Web3Context('http://127.0.0.1:8545'); | ||
| web3Context.registerPlugin(new CustomRpcMethodsPlugin()); | ||
| ``` | ||
|
|
||
| From the above code, the following is the module augmentation that's required to be declared by the plugin user: | ||
|
|
||
| ```typescript | ||
| declare module 'web3-core' { | ||
| interface Web3Context { | ||
| customRpcMethods: CustomRpcMethodsPlugin; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Now after augmenting the `Web3Context` interface from the `web3-core` module, we can have the below type safe code: | ||
|
|
||
| ##### `web3Context.customRpcMethods.customRpcMethod` | ||
|
|
||
|  | ||
|
|
||
| ##### `web3Context.customRpcMethods.customRpcMethodWithParameters` | ||
|
|
||
|  | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| --- | ||
| sidebar_position: 0 | ||
| sidebar_label: 'Plugin Authors' | ||
| --- | ||
|
|
||
| # Web3.js Plugin Author's Guide | ||
|
|
||
| This guide intends to provide the necessary context for developing plugins for Web3.js. | ||
|
|
||
| ## Before Getting Started | ||
|
|
||
| It's highly recommended you as the plugin author understand the limitations of TypeScript's module augmentation as described in the [main plugin guide](/docs/guides/web3_plugin_guide/), so you can communicate to your users that they are responsible for augmenting the class interface they register your plugin with if they desire to have type support when using your plugin. Ideally this could be solved for by the plugin author, or better yet Web3.js, but so far a better solution is unknown - if you have any ideas, please [create an issue](https://github.com/web3/web3.js/issues/new/choose) and help us improve Web3.js' UX. | ||
jdevcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## Plugin Dependencies | ||
|
|
||
| At the minimum, your plugin should depend on the `4.x` version of `web3-core`. This will allow your plugin class to extend the provided `Web3PluginBase` abstract class. However, `web3-core` shouldn't be listed as a regular dependency, instead it should be listed in your plugin's `package.json` as a [peer dependency](https://nodejs.org/en/blog/npm/peer-dependencies/): | ||
|
|
||
| ```json | ||
| { | ||
| "name": "web3-plugin-custom-rpc-methods", | ||
| "version": "0.0.1", | ||
| "peerDependencies": { | ||
| "web3-core": ">= 4.0.1-alpha.0 < 5" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| When your users install your plugin, this will allow the package manager to make use of the user installed `web3-core` if available and if the version satisfies the version constraints instead of installing it's own version of `web3-core`. | ||
|
|
||
| ## Extending `Web3PluginBase` | ||
|
|
||
| Your plugin class should `extend` the `Web3PluginBase` abstract class. This class `extends` [Web3Context](/api/web3-core/class/Web3Context) and when the user registers your plugin with a class, your plugin's `Web3Context` will point to the module's `Web3Context` giving your plugin access to things such as user configured [requestManager](/api/web3-core/class/Web3Context#requestManager) and [accountProvider](/api/web3-core/class/Web3Context#accountProvider). | ||
|
|
||
| ```typescript | ||
| import { Web3PluginBase } from 'web3-core'; | ||
|
|
||
| export class CustomRpcMethodsPlugin extends Web3PluginBase { ... } | ||
| ``` | ||
|
|
||
| ### `pluginNamespace` | ||
|
|
||
| After extending the `Web3PluginBase` class, your plugin will need a `public` `pluginNamespace` property that configures how your plugin will be accessed on the class your plugin was registered with. In the following example, the `pluginNamespace` is set to `customRpcMethods`, so when the user registers the plugin they will access your plugin as follows: | ||
|
|
||
| The following represents your plugin code: | ||
|
|
||
| ```typescript | ||
| // custom_rpc_methods_plugin.ts | ||
| import { Web3PluginBase } from 'web3-core'; | ||
|
|
||
| export class CustomRpcMethodsPlugin extends Web3PluginBase { | ||
| public pluginNamespace = 'customRpcMethods'; | ||
|
|
||
| public someMethod() { | ||
| return 'someValue'; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The following represents the plugin user's code: | ||
|
|
||
| ```typescript | ||
| // registering_a_plugin.ts | ||
| import { Web3Context } from './web3_export_helper'; | ||
| import { CustomRpcMethodsPlugin } from './custom_rpc_methods_plugin'; | ||
|
|
||
| declare module 'web3-core' { | ||
| interface Web3Context { | ||
| customRpcMethods: CustomRpcMethodsPlugin; | ||
| } | ||
| } | ||
|
|
||
| const web3Context = new Web3Context('http://127.0.0.1:8545'); | ||
| web3Context.registerPlugin(new CustomRpcMethodsPlugin()); | ||
|
|
||
| await web3Context.customRpcMethods.someMethod(); | ||
| ``` | ||
|
|
||
| ### Using the Inherited `Web3Context` | ||
|
|
||
| Below is an example of `CustomRpcMethodsPlugin` making use of `this.requestManager` which will have access to an Ethereum provider if one was configured by the user. In the event that no `provider` was set by the user, the below code will throw a [ProviderError](/api/web3-errors/class/ProviderError) if `customRpcMethod` was to be called: | ||
|
|
||
| ```typescript | ||
| import { Web3PluginBase } from 'web3-core'; | ||
|
|
||
| export class CustomRpcMethodsPlugin extends Web3PluginBase { | ||
| public pluginNamespace = 'customRpcMethods'; | ||
|
|
||
| public async customRpcMethod() { | ||
| return this.requestManager.send({ | ||
| method: 'custom_rpc_method', | ||
| params: [], | ||
| }); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Below is representing a plugin user's code that does not configure an Ethereum provider, resulting in a thrown [ProviderError](/api/web3-errors/class/ProviderError): | ||
|
|
||
| ```typescript | ||
| // registering_a_plugin.ts | ||
| import { Web3Context } from './web3_export_helper'; | ||
| import { CustomRpcMethodsPlugin } from './custom_rpc_methods_plugin'; | ||
|
|
||
| declare module 'web3-core' { | ||
| interface Web3Context { | ||
| customRpcMethods: CustomRpcMethodsPlugin; | ||
| } | ||
| } | ||
|
|
||
| const web3Context = new Web3Context(); | ||
| web3Context.registerPlugin(new CustomRpcMethodsPlugin()); | ||
|
|
||
| // The following would result in a thrown ProviderError when | ||
| // the plugin attempts to call this.requestManager.send(...) | ||
| await web3Context.customRpcMethods.customRpcMethod(); | ||
| ``` | ||
|
|
||
| Thrown [ProviderError](/api/web3-errors/class/ProviderError): | ||
|
|
||
| ```bash | ||
| ProviderError: Provider not available. Use `.setProvider` or `.provider=` to initialize the provider. | ||
| ``` | ||
|
|
||
| ### Providing an API Generic to `Web3PluginBase` | ||
|
|
||
| If needed, you can provide an API type (that follows the [Web3ApiSpec](/api/web3-types#Web3APISpec) pattern) as a generic to `Web3PluginBase` that will add type hinting to the `requestManager` when developing your plugin. In the below code, this is the `CustomRpcApi` type that's being passed as `Web3PluginBase<CustomRpcApi>` | ||
|
|
||
| ```typescript | ||
| import { Web3PluginBase } from 'web3-core'; | ||
|
|
||
| type CustomRpcApi = { | ||
| custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; | ||
| }; | ||
|
|
||
| export class CustomRpcMethodsPlugin extends Web3PluginBase<CustomRpcApi> { | ||
| public pluginNamespace = 'customRpcMethods'; | ||
|
|
||
| public async customRpcMethodWithParameters(parameter1: string, parameter2: number) { | ||
| return this.requestManager.send({ | ||
| method: 'custom_rpc_method_with_parameters', | ||
| params: [parameter1, parameter2], | ||
| }); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Using Web3.js Packages within Your Plugin | ||
|
|
||
| ### Overriding `Web3Context`'s `.link` Method | ||
|
|
||
| There currently exists [an issue](https://github.com/web3/web3.js/issues/5492) with certain Web3.js packages not correctly linking their `Web3Context` with the context of the class the user has registered the plugin with. As mentioned in the issue, this can result in a bug where a plugin instantiates an instance of `Contract` (from `web3-eth-contract`) and attempts to call a method on the `Contract` instance (which uses the `requestManager` to make a call to the Ethereum provider), resulting in a [ProviderError](/api/web3-errors/class/ProviderError) even though the plugin user has set a provider and it should be available to the plugin. | ||
|
|
||
| A workaround for this issue is available, below is an example of it: | ||
|
|
||
| ```typescript | ||
| import { Web3Context, Web3PluginBase } from 'web3-core'; | ||
| import { ContractAbi } from 'web3-eth-abi'; | ||
| import Contract from 'web3-eth-contract'; | ||
| import { Address } from 'web3-types'; | ||
| import { DataFormat, DEFAULT_RETURN_FORMAT, format } from 'web3-utils'; | ||
|
|
||
| import { ERC20TokenAbi } from './ERC20Token'; | ||
|
|
||
| export class ContractMethodWrappersPlugin extends Web3PluginBase { | ||
| public pluginNamespace = 'contractMethodWrappersPlugin'; | ||
|
|
||
| private readonly _contract: Contract<typeof ERC20TokenAbi>; | ||
|
|
||
| public constructor(abi: ContractAbi, address: Address) { | ||
| super(); | ||
| this._contract = new Contract(abi, address); | ||
| } | ||
|
|
||
| /** | ||
| * This method overrides the inherited `link` method from `Web3PluginBase` | ||
| * to add to a configured `RequestManager` to our Contract instance | ||
| * when `Web3.registerPlugin` is called. | ||
| * | ||
| * @param parentContext - The context to be added to the instance of `ChainlinkPlugin`, | ||
| * and by extension, the instance of `Contract`. | ||
| */ | ||
| public link(parentContext: Web3Context) { | ||
| super.link(parentContext); | ||
| this._contract.link(parentContext); | ||
| } | ||
|
|
||
| public async getFormattedBalance<ReturnFormat extends DataFormat>( | ||
| address: Address, | ||
| returnFormat?: ReturnFormat, | ||
| ) { | ||
| return format( | ||
| { eth: 'unit' }, | ||
| await this._contract.methods.balanceOf(address).call(), | ||
| returnFormat ?? DEFAULT_RETURN_FORMAT, | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The workaround is overwriting the inherited `link` method (inherited from `Web3PluginBase` which inherits it from `Web3Context`) and explicitly calling `.link` on the `Contract` instance. The `parentContext` will get passed when the user calls `registerPlugin`, it will be the context of the class the user is registering your plugin with. | ||
|
|
||
| The following is the workaround, and will probably need to be done for any instantiated Web3.js package your plugin uses that makes use of `Web3Context`: | ||
|
|
||
| ```typescript | ||
| public link(parentContext: Web3Context) { | ||
| super.link(parentContext); | ||
| // This workaround will ensure the context of the Contract | ||
| // instance is linked to the context of the class the | ||
| // plugin user is registering the plugin with | ||
| this._contract.link(parentContext); | ||
| } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.