本文档介绍了OpenZeppelin Relayer的插件系统,该系统允许开发者通过TypeScript函数扩展Relayer的功能。文章详细说明了插件的配置方式、开发指南、调用方法以及调试技巧,并提供了完整的示例。此外,还讨论了从旧模式迁移到推荐的handler模式的步骤和兼容性。
OpenZeppelin Relayer 支持插件以扩展 relayer 的功能。
插件是 TypeScript
函数,运行在 Relayer 服务器中,可以包含 Relayer 运营商定义的任何任意逻辑。
插件系统具有以下特点:
插件在 plugins
目录下声明,并且应该是 TypeScript 文件(.ts
扩展名)。
openzeppelin-relayer/
├── plugins/
│ └── my-plugin.ts # 插件代码
└── config/
└── config.json # 配置文件中的插件
此方法使用简单的 handler
导出模式:
/// 必需的导入。
import { Speed, PluginAPI } from "@openzeppelin/relayer-sdk";
/// 定义你的插件参数接口
type MyPluginParams = {
destinationAddress: string;
amount?: number;
message?: string;
relayerId?: string;
}
/// 定义你的插件返回类型
type MyPluginResult = {
success: boolean;
transactionId: string;
message: string;
}
/// 导出一个 handler 函数 - 就这样!
export async function handler(api: PluginAPI, params: MyPluginParams): Promise<MyPluginResult> {
console.info("🚀 插件已启动...");
// 验证参数
if (!params.destinationAddress) {
throw new Error("destinationAddress is required");
}
// 使用 relayer API
const relayer = api.useRelayer(params.relayerId || "my-relayer");
const result = await relayer.sendTransaction({
to: params.destinationAddress,
value: params.amount || 1,
data: "0x",
gas_limit: 21000,
speed: Speed.FAST,
});
console.info(`Transaction submitted: ${result.id}`);
// 等待确认
await result.wait({
interval: 5000, // 每 5 秒检查一次
timeout: 120000 // 2 分钟后超时
});
return {
success: true,
transactionId: result.id,
message: `Successfully sent ${params.amount || 1} wei to ${params.destinationAddress}`
};
}
runPlugin() 模式已被弃用,并将在未来的版本中移除。请迁移到上面的 handler 导出模式。传统的插件将继续工作,但会显示弃用警告。 |
import { runPlugin, PluginAPI } from "./lib/plugin";
async function myPlugin(api: PluginAPI, params: any) {
// 在这里编写插件逻辑
return "result";
}
runPlugin(myPlugin); // ⚠️ 已弃用 - 显示警告但仍然有效
示例传统插件(plugins/examples/example-deprecated.ts
):
import { PluginAPI, runPlugin } from "../lib/plugin";
import { Speed } from "@openzeppelin/relayer-sdk";
type Params = {
destinationAddress: string;
};
async function example(api: PluginAPI, params: Params): Promise<string> {
console.info("插件已启动...");
const relayer = api.useRelayer("sepolia-example");
const result = await relayer.sendTransaction({
to: params.destinationAddress,
value: 1,
data: "0x",
gas_limit: 21000,
speed: Speed.FAST,
});
await result.wait();
return "done!";
}
runPlugin(example);
插件在 ./config/config.json
文件中配置,位于 plugins
键下。
该文件包含一个插件列表,每个插件都有一个 id、路径和超时时间(以秒为单位)(可选)。
插件路径是相对于 /plugins 目录的 |
示例:
"plugins": [\
{\
"id": "my-plugin",\
"path": "my-plugin.ts",\
"timeout": 30\
}\
]
超时是插件可以运行的最大时间(以秒为单位)。如果插件超过超时时间,它将终止并显示错误。
超时是可选的,如果未提供,则默认为 300 秒(5 分钟)。
定义参数类型:始终为插件参数创建接口或类型
定义返回类型:指定插件返回的内容,以获得更好的开发体验
优雅地处理错误:使用 try-catch 代码块并返回结构化的错误响应
验证输入:检查必需的参数并提供有意义的错误消息
使用 Async/Await:现代的异步模式,以获得更好的可读性
你可以直接测试你的 handler 函数:
import { handler } from './my-plugin';
// 用于测试的模拟 API(在实际场景中,使用适当的模拟)
const mockApi = {
useRelayer: (id: string) => ({
sendTransaction: async (tx: any) => ({ id: "test-tx-123", wait: async () => {} })
})
} as any;
const result = await handler(mockApi, {
destinationAddress: "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A",
amount: 1000
});
console.log(result);
插件通过访问 api/v1/plugins/{plugin-id}/call
端点来调用。
该端点接受 POST
请求。示例 post 请求体:
{
"destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A",
"amount": 1000000000000000,
"message": "Hello from OpenZeppelin Relayer!"
}
这些参数直接传递到你的插件的 handler
函数中。
当调用插件时,响应将包括:
logs
:插件执行的日志。
return_value
:插件执行的返回值。
error
:如果插件执行失败,则显示错误消息。
traces
:插件和 Relayer 实例之间发送的消息列表。这包括通过 PluginAPI
对象传递的所有 payload。
plugins/example.ts
):import { Speed, PluginAPI } from "@openzeppelin/relayer-sdk";
type ExampleParams = {
destinationAddress: string;
amount?: number;
message?: string;
}
type ExampleResult = {
success: boolean;
transactionId: string;
transactionHash: string | null;
message: string;
timestamp: string;
}
export async function handler(api: PluginAPI, params: ExampleParams): Promise<ExampleResult> {
console.info("🚀 示例插件已启动");
console.info(`📋 参数:`, JSON.stringify(params, null, 2));
try {
// 验证参数
if (!params.destinationAddress) {
throw new Error("destinationAddress is required");
}
const amount = params.amount || 1;
const message = params.message || "Hello from OpenZeppelin Relayer!";
console.info(`💰 发送 ${amount} wei 到 ${params.destinationAddress}`);
// 获取 relayer 并发送交易
const relayer = api.useRelayer("my-relayer");
const result = await relayer.sendTransaction({
to: params.destinationAddress,
value: amount,
data: "0x",
gas_limit: 21000,
speed: Speed.FAST,
});
console.info(`✅ 交易已提交: ${result.id}`);
// 等待确认
const confirmation = await result.wait({
interval: 5000,
timeout: 120000
});
console.info(`🎉 交易已确认: ${confirmation.hash}`);
return {
success: true,
transactionId: result.id,
transactionHash: confirmation.hash || null,
message: `已成功发送 ${amount} wei 到 ${params.destinationAddress}。${message}`,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error("❌ 插件执行失败:", error);
return {
success: false,
transactionId: "",
transactionHash: null,
message: `插件失败: ${(error as Error).message}`,
timestamp: new Date().toISOString()
};
}
}
config/config.json
):{
"plugins": [\
{\
"id": "example-plugin",\
"path": "example-plugin.ts",\
"timeout": 30\
}\
]
}
curl -X POST http://localhost:8080/api/v1/plugins/example-plugin/call \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A",
"amount": 1000000000000000,
"message": "Test transaction from plugin"
}'
{
"success": true,
"message": "Plugin called successfully",
"logs": [\
{\
"level": "info",\
"message": "🚀 Example plugin started"\
},\
{\
"level": "info",\
"message": "💰 Sending 1000000000000000 wei to 0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A"\
},\
{\
"level": "info",\
"message": "✅ Transaction submitted: tx-123456"\
},\
{\
"level": "info",\
"message": "🎉 Transaction confirmed: 0xabc123..."\
}\
],
"return_value": {
"success": true,
"transactionId": "tx-123456",
"transactionHash": "0xabc123def456...",
"message": "Successfully sent 1000000000000000 wei to 0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A. Test transaction from plugin",
"timestamp": "2024-01-15T10:30:00.000Z"
},
"error": "",
"traces": [\
{\
"relayer_id": "my-relayer",\
"method": "sendTransaction",\
"payload": {\
"to": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A",\
"value": "1000000000000000",\
"data": "0x",\
"gas_limit": 21000,\
"speed": "fast"\
}\
}\
]
}
logs
:插件的终端输出(console.log、console.error 等)
return_value
:插件的 handler 函数返回的值
error
:如果插件执行失败,则显示错误消息
traces
:通过 PluginAPI 在插件和Relayer 实例之间交换的消息
✅ 传统插件仍然有效 - 无需立即采取措施
⚠️ 弃用警告 - 传统插件将显示控制台警告
📅 未来移除 - runPlugin
模式将在未来的主要版本中移除
🎯 推荐操作 - 迁移到 handler 模式以开发新插件
如果你有使用 runPlugin()
的现有插件,则迁移很简单:
之前(传统模式 - 仍然有效):
import { runPlugin, PluginAPI } from "./lib/plugin";
async function myPlugin(api: PluginAPI, params: any): Promise<any> {
// 你的插件逻辑
return result;
}
runPlugin(myPlugin); // ⚠️ 显示弃用警告
之后(现代模式 - 推荐):
import { PluginAPI } from "@openzeppelin/relayer-sdk";
export async function handler(api: PluginAPI, params: any): Promise<any> {
// 相同的插件逻辑 - 只需导出为 handler!
return result;
}
删除文件底部的 runPlugin()
调用
将你的函数重命名为 handler
(或创建一个新的 handler 导出)
使用 export async function handler
导出 handler
函数
添加适当的 TypeScript 类型,以获得更好的开发体验
测试你的插件,以确保它在新模式下工作
更新你的文档,以反映新的模式
relayer 将自动检测你的插件使用哪种模式:
如果它找到一个 handler
导出 → 使用现代模式
如果没有 handler
,但是调用了 runPlugin()
→ 使用带有警告的传统模式
如果都没有 → 显示明确的错误消息
这确保了一个平稳的过渡期,其中两种模式可以同时工作。
- 原文链接: docs.openzeppelin.com/re...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!