web3.js 插件开发指南

web3.js 插件开发指南

Flying
2023-09-12 / 0 评论 / 77 阅读 / 正在检测是否收录...

除了 web3.js 标准库之外,从 4.0.2 开始 web3.js 支持插件,它为最终用户添加了特定的功能。这些额外的功能可以是特定契约的包装器、额外的 RPC 方法包装器,甚至可以扩展 web3.js 方法的逻辑。本指南旨在为开发 web3.js 插件提供必要的上下文信息。

web3js-plugin.svg

请随意探索一些已构建的插件和 / 或使用这个模板来开始开发你的 Web3.js 插件。

为了为插件用户提供类型安全性和 IntelliSense,请参考设置模块增强部分,了解如何扩展 Web3Context 模块以为插件启用类型特性。

插件依赖项

最少,你的插件应该依赖于 web3 包的 4.0.2 版本。这将允许你的插件类扩展提供的 Web3PluginBase 抽象类。但是,web3 不应该被列为常规依赖项,而是应该在你的插件的 package.json 中作为对等依赖项列出。

需要注意的是,插件名称应该结构化为 @<organization>/web3-plugin-<name>web3-plugin-<name>
{
  "name": "web3-plugin-custom-rpc-methods",
  "version": "0.1.0",
  "peerDependencies": {
    "web3": ">= 4.0.2 < 5"
  }
}

当用户安装你的插件时,这将允许包管理器在用户已安装的 web3 可用且版本满足版本约束的情况下使用用户安装的 web3,而不是安装自己的 web3 版本。

扩展 Web3PluginBase

你的插件类应该 extend(继承)Web3PluginBase 抽象类。此类 extends Web3Context 当用户使用类注册你的插件时,你的插件的 Web3Context 将指向模块的 Web3Context,从而使你的插件可以访问用户配置的 requestManageraccountProvider 等内容。

import { Web3PluginBase } from 'web3';

export class CustomRpcMethodsPlugin extends Web3PluginBase { ... }

扩展 Web3EthPluginBase

除了 Web3PluginBase,你还可以选择扩展 Web3EthPluginBase,它将提供Ethereum JSON RPC API 接口,这是一些包(例如 Web3Eth)使用的,作为你的插件的 requestManager 的泛型,为Ethereum JSON RPC 规范提供类型支持。如果你的插件直接使用 web3 提供的 requestManager 向提供程序发出 Ethereum JSON RPC 调用,这将是推荐的方法。

import { Web3EthPluginBase } from 'web3';

export class CustomRpcMethodsPlugin extends Web3EthPluginBase { ... }

插件命名空间

在扩展 Web3PluginBase 类之后,你的插件将需要一个 publicpluginNamespace 属性,该属性配置了在用户注册插件的类上如何访问你的插件。在下面的例子中,pluginNamespace 被设置为 customRpcMethods,因此当用户注册插件时,他们将以以下方式访问你的插件:

以下是你的插件代码:

// custom_rpc_methods_plugin.ts
import { Web3PluginBase } from 'web3';

export class CustomRpcMethodsPlugin extends Web3PluginBase {
  public pluginNamespace = 'customRpcMethods';

  public someMethod() {
    return 'someValue';
  }
}

以下是插件用户的代码:

// registering_a_plugin.ts
import { Web3Context } from 'web3';

import { CustomRpcMethodsPlugin } from './custom_rpc_methods_plugin';

const web3Context = new Web3Context('http://127.0.0.1:8545');
web3Context.registerPlugin(new CustomRpcMethodsPlugin());

await web3Context.customRpcMethods.someMethod();

使用继承的 Web3Context

下面是 CustomRpcMethodsPlugin 利用 this.requestManager 的示例,该属性将访问 Ethereum 提供程序(如果用户已配置提供程序)。如果用户没有设置提供程序,则在调用 customRpcMethod 时,以下代码将抛出一个 ProviderError

import { Web3PluginBase } from 'web3';

export class CustomRpcMethodsPlugin extends Web3PluginBase {
  public pluginNamespace = 'customRpcMethods';

  public async customRpcMethod() {
    return this.requestManager.send({
      method: 'custom_rpc_method',
      params: []
    });
  }
}

以下是不配置 Ethereum 提供程序的插件用户代码,当调用 customRpcMethod 时,将抛出 ProviderError

// registering_a_plugin.ts
import { Web3Context } from 'web3'

import { CustomRpcMethodsPlugin } from './custom_rpc_methods_plugin';

const web3Context = new Web3Context();
web3Context.registerPlugin(new CustomRpcMethodsPlugin());

// 当插件尝试调用 this.requestManager.send(...) 时,以下操作将抛出 ProviderError
await web3Context.customRpcMethods.customRpcMethod();

抛出的 ProviderError

ProviderError: Provider not available. Use `.setProvider` or `.provider=` to initialize the provider.

为 Web3PluginBase 提供 API 泛型

如果需要,你可以为 Web3PluginBase 提供一个 API 类型(遵循 Web3ApiSpec模式),作为泛型传递给 Web3PluginBase,它将为 requestManager 提供类型提示,从而在开发插件时为其提供类型提示。在下面的代码中,CustomRpcApi 是传递给 Web3PluginBase<CustomRpcApi> 的类型。

import { Web3PluginBase } from 'web3';

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]
    })
  }
}

在你的插件中使用 web3.js 包

当前存在一个问题,即某些 web3.js 包未能正确将它们的 Web3Context 链接到用户注册插件的类的上下文。正如在问题中所述,这可能导致一个 bug,其中插件实例化一个 Contract(来自 web3-eth-contract)的实例并尝试在 Contract 实例上调用方法(它使用 requestManager 调用 Ethereum 提供程序的方法),从而导致 ProviderError 错误,尽管插件用户已设置了提供程序,并且它应该对插件可用。

该问题的解决方法如下所示:

import { Contract, ContractAbi, Web3Context, Web3PluginBase, types, utils } from 'web3';

import { ERC20TokenAbi } from './ERC20Token';

export class ContractMethodWrappersPlugin extends Web3PluginBase {
  public pluginNamespace = 'contractMethodWrappersPlugin';

  private readonly _contract: Contract<typeof ERC20TokenAbi>;

  public constructor(abi: ContractAbi, address: types.Address) {
    super();
    this._contract = new Contract(abi, address);
  }

  /**
   * 此方法覆盖了继承的 `link` 方法,以在 `Web3.registerPlugin` 被调用时,为 `Contract` 实例添加一个配置好的 `RequestManager`。
   *
   * @param parentContext - 要添加到 `ChainlinkPlugin` 实例(从而添加到 `Contract` 实例)的上下文。
   */
  public link(parentContext: Web3Context) {
    super.link(parentContext);
    this._contract.link(parentContext);
  }

  public async getFormattedBalance<ReturnFormat extends types.DataFormat>(
    address: types.Address,
    returnFormat?: ReturnFormat
  ) {
    return utils.format(
      { eth: 'unit' },
      await this._contract.methods.balanceOf(address).call(),
      returnFormat ?? types.DEFAULT_RETURN_FORMAT
    );
  }
}

这个解决方法是覆盖继承的 link 方法(从 Web3PluginBase 继承的,它又从 Web3Context 继承而来),并明确调用 Contract 实例上的 .link 方法。当用户调用 registerPlugin 时,将传递 parentContext,它将是用户正在注册插件的类的上下文。

这个解决方法将覆盖继承的 link 方法(从 Web3PluginBase 继承,它又继承自 Web3Context),并明确调用 Contract 实例上的 .link 方法。当用户调用 registerPlugin 时,将传递 parentContext,它将是用户正在注册插件的类的上下文。

以下是解决方法,并且可能需要对插件使用的任何实例化的 web3.js 包进行相同的操作,这些包使用 Web3Context

public link(parentContext: Web3Context) {
  super.link(parentContext);
  // 这个解决方法将确保 Contract 实例的上下文与插件用户正在注册插件的类的上下文相链接
  this._contract.link(parentContext);
}

设置模块增强

为了为插件注册时提供类型安全性和 IntelliSense,你必须扩充 Web3Context 模块。简单来说,你将告诉 TypeScript,你正在修改 Web3Context 类的接口,并在其中包括你的插件的接口(即你的插件的添加方法、属性等)。结果,你的插件对象将以你选择的命名空间的形式可用,并将在任何 Web3Context 对象中可用。

关于模块增强的更多信息,可以参考这里的教程

模块增强

在注册插件时,你正在向模块的接口中添加额外的方法和 / 或类,并且 TypeScript 需要一些帮助来理解在插件注册后模块中会有哪些内容可用。

// custom_rpc_methods_plugin.ts
import { Web3PluginBase } from 'web3';

export class CustomRpcMethodsPlugin extends Web3PluginBase {
  public pluginNamespace = 'customRpcMethods';

  public someMethod() {
    return 'someValue';
  }
}

// 模块增强
declare module 'web3' {
  // 这里是在 Web3Context 类内添加你的插件的地方
  interface Web3Context {
    customRpcMethods: CustomRpcMethodsPlugin;
  }
}

需要考虑的重要点

  1. 通过扩展 Web3Context(及其所有扩展它的类),你的插件的接口将出现在所有扩展 Web3Context 的 Web3 模块(即 web3web3-ethweb3-eth-contract 等)的 IntelliSense 中。即使

你的插件未注册,这是值得让用户知道的事情,因为他们只有在使用 .registerPlugin 时才能使用你的插件。

这是值得让用户知道的事情,因为他们只有在使用 .registerPlugin 时才能使用你的插件。以下是你的插件用户 在不调用 .registerPlugin 的情况下会看到的内容:

web3-context-augmentation.png

上面截图显示了 .customRpcMethods.someMethod 可以在 Web3 实例上调用的智能提示,无论插件用户是否注册了 CustomRpcMethodsPlugin。但是,如果用户在访问插件功能之前没有调用 .registerPlugin,那么他们会遇到错误。你需要明确告诉他们在访问插件的任何功能之前,他们需要调用 .registerPlugin

  1. registerPlugin 方法存在于 Web3Context 类上,因此任何 extends Web3Context 的类都可以将你的插件的额外功能添加到其接口中。因此,通过扩充 Web3Context 以包括你的插件的接口,实际上你提供了一个通用的扩充,将你的插件的接口添加到所有扩展 Web3Context 的 Web3 模块上(例如 web3web3-ethweb3-eth-contract 等)。
  2. 我们在示例代码中使用 customRpcMethods 作为插件的 pluginNamespace 的名称,请注意在两个地方使用相同的名称:第一个地方是在模块增强中。第二个地方是在你的插件类中的公共 pluginNamespace 属性的值。

所以,例如,请注意在下面的两个代码片段中使用 customRpcMethods

模块增强:

// 由 **插件开发者**编写的代码

declare module 'web3' {
  // 这里是在 Web3Context 类内添加你的插件
  interface Web3Context {
    customRpcMethods: CustomRpcMethodsPlugin
  }
}

你的插件类:

// 由 **插件开发者**编写的代码

export class CustomRpcMethodsPlugin extends Web3PluginBase {
  public pluginNamespace = 'customRpcMethods';
  ...
}

这是因为 .registerPlugin 将使用插件提供的 pluginNamespace 属性作为属性名称,当它将插件注册到 插件用户 将调用 .registerPlugin 的类实例上时:

// 由 **插件用户**编写的代码

const web3 = new Web3('http://127.0.0.1:8545');
web3.registerPlugin(new CustomRpcMethodsPlugin());
// 现在 customRpcMethods(即 pluginNamespace)在 Web3 实例上是可用的
web3.customRpcMethods;

完整示例

你可能会发现参考一个完整的 web3 插件开发和使用示例是有帮助的。Web3.js Chainlink Plugin 存储库提供了一个非常好的示例,可以参考一下。

0

评论 (0)

取消