Actions
Actions
Actions 是自动化的 JavaScript 代码段,可以通过触发器执行。

触发器
支持以下触发器:
-
计划: 选择一个频率,Defender 将按指定的间隔调用该函数。请注意,指定的间隔是两个连续执行开始之间的时间间隔,_而不是_一个运行结束和下一个运行开始之间的时间间隔。或者,可以使用 cron 表达式 指定操作的运行时间。
-
Webhook: Defender 将为该操作创建一个 webhook URL,每当向该端点发送 HTTP POST 请求时,该 URL 将被执行。该 URL 可以随时重新生成。调用后,HTTP 请求信息将被注入到操作中,并且 HTTP 响应将包括操作运行信息以及从操作返回的任何数据。
重要提示: 当向 webhook 发送请求时,请确保包含 Content-Type: application/json
头部。我们严格要求请求中必须存在这个头部。否则,你将看到 415 Unsupported Media Type
错误。
-
Monitor: 由 Defender Monitor 触发。它将包含一个 body 属性,其中包含触发事件的详细信息,你可以使用这些信息来运行自定义逻辑。
环境
Actions 在一个具有 256mb RAM 和 5 分钟超时时间的 node 20 环境中运行。每个操作的代码大小必须小于 5mb。为了便于使用,环境中预安装了一组常用依赖项。最新的 action 依赖版本 v2025-01-16
具有以下依赖项:
"@datadog/datadog-api-client": "^1.0.0-beta.5",
"@fireblocks/fireblocks-web3-provider": "^1.3.1",
"@gnosis.pm/safe-core-sdk": "^0.3.1",
"@gnosis.pm/safe-ethers-adapters": "^0.1.0-alpha.3",
"@openzeppelin/defender-admin-client": "1.54.6",
"@openzeppelin/defender-autotask-client": "1.54.6",
"@openzeppelin/defender-autotask-utils": "1.54.6",
"@openzeppelin/defender-kvstore-client": "1.54.6",
"@openzeppelin/defender-relay-client": "1.54.6",
"@openzeppelin/defender-sdk": "1.15.2",
"@openzeppelin/defender-sentinel-client": "1.54.6",
"axios": "^1.7.4",
"axios-retry": "3.5.0",
"ethers": "5.5.3",
"fireblocks-sdk": "^2.5.4",
"graphql": "^15.5.1",
"graphql-request": "3.4.0",
"web3": "1.9.0"
不在 @openzeppelin 命名空间下的 Defender 依赖项现在已被弃用。受影响的依赖项包括 defender-admin-client,defender-autotask-client,defender-autotask-utils,defender-kvstore-client,defender-relay-client,defender-sentinel-client
|
如果需要其他依赖项,则可以使用 JavaScript 模块打包器,例如 rollup 或 webpack。参考 这个示例项目 了解如何操作。请与我们联系,添加你认为其他用户会觉得有用的依赖项! |
我们 OpenZeppelin 开发团队非常喜欢 Typescript,我们也希望你也喜欢!如果你想用 TypeScript 编写你的 actions,你需要首先使用 tsc 或你选择的打包器编译它们,然后上传生成的 JavaScript 代码。不幸的是,我们不支持在用户界面中直接使用 TypeScript 编码。所有 defender-sdk 包都是用 TypeScript 编写的,并且打包了它们的类型声明。你也可以使用 openzeppelin/defender-sdk-action-client 包来获取事件负载的类型定义。
|
运行时升级
Actions 必须与最新的 Node.js 运行时版本保持同步,以确保它们在最新的安全环境中运行。有时,我们会强制对所有 Actions 进行运行时升级,以达到最低运行时版本。该过程包括:
-
在自动升级之前发送电子邮件通知。
-
在 actions 页面下显示 UI 横幅,以警告即将进行的自动升级。
-
发布论坛公告。
-
在自动升级当天,Defender 会自动将所有 action 运行时升级到最低要求的版本。
我们建议你检查你的 Actions,进行必要的更改,并提前升级,以防止任何重大更改。 |
定义代码
处理函数
你的代码必须导出一个异步 handler
函数,该函数将在每次执行 action 时被调用。
exports.handler = async function(event) {
// Your code here
// 你的代码
}
以下接口包含 Defender 在调用 Action 时注入的 event
类型:
export interface ActionEvent {
/**
* Internal identifier of the relayer function used by the relay-client
*/
relayerARN?: string;
/**
* Internal identifier of the key-value store function used by the kvstore-client
*/
kvstoreARN?: string;
/**
* Internal credentials generated by Defender for communicating with other services
*/
credentials?: string;
/**
* Read-only key-value secrets defined in the Action secrets vault
*/
secrets?: ActionSecretsMap;
/**
* Contains a Webhook request, Monitor match information, or Monitor match request
*/
request?: ActionRequestData;
/**
* actionId is the unique identifier of the Action
*/
actionId: string;
/**
* Name assigned to the Action
*/
actionName: string;
/**
* Id of the the current Action run
*/
actionRunId: string;
/**
* Previous Action run information
*/
previousRun?: PreviousActionRunInfo;
}
Relayer 集成
如果将你的自动 action 连接到 relayer,则 Defender 将自动注入临时凭据,以便从 action 代码访问 relayer。只需将事件对象传递给 relayer 客户端,以代替凭据:
const { Defender } = require('@openzeppelin/defender-sdk');
exports.handler = async function(event) {
const client = new Defender(event);
// Use relayer for sending txs or querying the network...
// 使用 relayer 发送交易或查询网络...
}
这允许你从 actions 发送交易,而无需设置任何 API 密钥或密钥。此外,你还可以使用 relayer 的 JSON RPC 端点来查询任何 Ethereum 网络,而无需为外部网络提供商配置 API 密钥。
我们还支持 ethers.js
通过 relayer 进行查询或发送交易。要使用 ethers.js,请将以上代码段替换为以下代码:
const { DefenderRelaySigner, DefenderRelayProvider } = require('defender-relay-client/lib/ethers');
const ethers = require('ethers');
exports.handler = async function(event) {
const provider = new DefenderRelayProvider(event);
const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });
// Use provider and signer for querying or sending txs from ethers, for example...
// 使用 provider 和 signer 从 ethers 进行查询或发送交易,例如...
const contract = new ethers.Contract(ADDRESS, ABI, signer);
await contract.ping();
}
如果你喜欢 web3.js
:
const { DefenderRelayProvider } = require('defender-relay-client/lib/web3');
const Web3 = require('web3');
exports.handler = async function(event) {
const provider = new DefenderRelayProvider(event, { speed: 'fast' });
const web3 = new Web3(provider);
// Use web3 instance for querying or sending txs, for example...
// 使用 web3 实例进行查询或发送交易,例如...
const [from] = await web3.eth.getAccounts();
const contract = new web3.eth.Contract(ABI, ADDRESS, { from });
await contract.methods.ping().send();
}
Monitor 调用
从 Monitor 触发的 Actions 可以有两种类型的 body 属性和 scheme,具体取决于触发 action 的 Monitor 类型:
-
在 Defender monitor 的情况下,body 将包含 monitor 事件 schema。
如果 action 是用 TypeScript 编写的,则可以使用 defender-sdk-action-client 包中的 BlockTriggerEvent
类型。
exports.handler = async function(params) {
const payload = params.request.body;
const matchReasons = payload.matchReasons;
const sentinel = payload.sentinel;
// if contract monitor
// 如果是合约 monitor
const transaction = payload.transaction;
const abi = sentinel.abi;
// custom logic...
// 自定义逻辑...
}
Webhook 调用
当通过 webhook 调用 action 时,它可以访问 HTTP 请求信息,作为注入到 handler 中的 event
参数的一部分。同样,返回值将包含在 HTTP 响应负载的 result
字段中。
exports.handler = async function(event) {
const {
body, // Object with JSON-parsed POST body
// 具有 JSON 解析的 POST body 的对象
headers, // Object with key-values from HTTP headers
// 具有来自 HTTP header 的键值的对象
queryParameters, // Object with key-values from query parameters
// 具有来自 query parameters 的键值的对象
} = event.request;
return {
hello: 'world' // JSON-serialized and included in the `result` field of the response
// JSON 序列化并包含在响应的 `result` 字段中
};
}
目前仅支持 JSON 负载,并且仅将带有 X-
或 Stripe-
前缀的非标准 header 提供给 action。
来自 webhook 端点的示例响应如下所示,其中 status
是 success
或 error
之一,encodedLogs
具有来自运行的 base64 编码的日志,result
具有从执行返回的 JSON 编码值。
{
"autotaskRunId": "37a91eba-9a6a-4404-95e4-38d178ba69ed",
"autotaskId": "19ef0257-bba4-4723-a18f-67d96726213e",
"trigger": "webhook",
"status": "success",
"createdAt": "2021-02-23T18:49:14.812Z",
"encodedLogs": "U1RBU...cwkK",
"result": "{\"hello\":\"world\"}",
"requestId": "e7979150-44d3-4021-926c-9d9679788eb8"
}
超过 25 秒才能完成的 Actions 将返回具有挂起状态的响应。但是,action 将继续在后台运行并最终完成(在 5 分钟内)。 |
如果 {"message":"Missing Authentication Token"} 是对 Webhook HTTP 请求的响应,请仔细检查该请求实际上是否为 POST。发出 GET 请求时会发生此响应。
|
Webhook 请求具有严格的 content-type 要求。如果请求没有 Content-Type: application/json 头部,则 action 调用将返回 415 Unsupported Media Type 错误。请确保在你的请求中包含此头部。
|
密钥
Defender 密钥允许你存储敏感信息,例如可以从 actions 安全访问的 API 密钥和密钥。+
Action 密钥是字符串的键值区分大小写对,可以使用 event.secrets
对象从 action 代码访问。action 使用的密钥数量没有限制。密钥在所有 actions 之间共享,而不是特定于单个 action。
exports.handler = async function(event) {
const { mySecret, anApiKey } = event.secrets;
}
密钥经过加密并存储在安全存储库中,仅在 action 运行时解密以进行注入。写入后,密钥只能从用户界面删除或覆盖,而不能读取。
action 可能会记录密钥的值,从而意外地泄漏它。 |
虽然可以使用密钥来存储用于签署消息或交易的私钥,但我们建议改用 Defender relayer。与在 action 代码中加载私钥并在那里签名相比,Defender relayers 的签名操作提供了更高的安全性。 |
键值数据存储
action 键值数据存储允许在 action 运行之间以及不同的 actions 之间持久保存简单数据。它可以用于存储交易标识符、哈希的用户电子邮件,甚至小型序列化对象。
你可以通过 Defender
实例与你的键值存储进行交互,该实例使用注入到你的 Action handler
函数中的负载进行初始化。初始化后,你可以调用 kvstore.get
、kvstore.put
或 kvstore.del
。
const { Defender } = require('@openzeppelin/defender-sdk');
exports.handler = async function (event) {
const client = new Defender(event);
await client.keyValueStore.put('myKey', 'myValue');
const value = await client.keyValueStore.get('myKey');
await client.keyValueStore.del('myKey');
};
键值存储允许获取、放置和删除键值对,这些键值对必须是限制为 1 KB 的字符串,值限制为 300 KB。
存储的数据在所有 actions 之间共享。为了隔离每个 action 管理的记录,建议使用每个 action 唯一的命名空间为键添加前缀。 |
每个项目在上次更新后 90 天到期。如果需要长期存在的数据存储,我们建议设置一个外部数据库,并使用 action 密钥来存储连接到它的凭据。 |
通知
Actions 可以通过 Defender 通知设置中已定义的各种渠道发送通知。此集成允许你快速通知其他连接的系统关于 actions 检测或进行的更改。
要发送通知,你应该使用 notificationClient.send()
,如以下示例所示:
exports.handler = async function(credentials, context) {
const { notificationClient } = context;
try {
notificationClient.send({
channelAlias: 'example-email-notification-channel-alias',
subject: 'Action notification example',
message: 'This is an example of a email notification sent from an action',
});
} catch (error) {
console.error('Failed to send notification', error);
}
}
对于电子邮件通知,支持基本的 HTML 标签。以下是如何生成 HTML 消息的示例:
function generateHtmlMessage(actionName, txHash) {
return `
<h1>Transaction sent from Action ${actionName}</h1>
<p>Transaction with hash <i>${txHash}</i> was sent.</p>
`;
}
exports.handler = async function(event, context) {
const { notificationClient } = context;
const relayer = new Relayer(credentials);
const txRes = await relayer.sendTransaction({
to: '0xc7464dbcA260A8faF033460622B23467Df5AEA42',
value: 100,
speed: 'fast',
gasLimit: '21000',
});
try {
notificationClient.send({
channelAlias: 'example-email-notification-channel-alias',
subject: `Transaction sent from Action ${event.actionName}`,
message: generateHtmlMessage(event.actionName, txRes.hash),
});
} catch (error) {
console.error('Failed to send notification', error);
}
}
要发送指标通知,请改用 notificationClient.sendMetric()
方法,如以下示例所示:
exports.handler = async function(credentials, context) {
const { notificationClient } = context;
try {
notificationClient.sendMetric({
channelAlias: 'example-email-notification-channel-alias',
name: 'datadog-test-metric',
value: 1,
});
} catch (error) {
console.error('Failed to send notification', error);
}
}
如果传递了无效或暂停的 notification channelAlias,则会抛出错误。 |
如果由于任何其他原因无法发送通知,则不会抛出错误,但会将状态消息添加到 action 日志中。例如,如果发送到具有不活动 URL 的 webhook 渠道的通知,则会添加日志条目,但不会抛出错误。 |
如果多个通知渠道使用相同的别名,则通知将发送给所有这些渠道。 |
错误处理
导致错误的自动 action 调用在 action 运行响应中包含一个 errorType
字段,该字段将设置为 defender-sdk 中定义的 ActionErrorType。用户可读的错误也会出现在“运行历史记录”视图中。
本地开发
如果想在本地重现 action 的行为以进行调试或测试,请按照下列步骤操作:
-
初始化一个新的 npm 项目 (
npm init
) -
将
package.json
中的dependencies
键设置为上面 Environment 部分中指示的包 -
下载
yarn.lock
: 📎 yarn.lock -
运行
yarn install --frozen-lockfile
。
你还可以使用以下模板进行本地开发,该模板将在使用 node
调用时运行 action 代码。它将从环境变量加载 relayer 凭据,或者在使用 Defender 运行时使用注入的凭据。
const { Defender } = require('@openzeppelin/defender-sdk');
// Entrypoint for the action
// action 的入口点
exports.handler = async function(event) {
const client = new Defender(credentials);
// Use client.relaySigner for sending txs
// 使用 client.relaySigner 发送 txs
}
// To run locally (this code will not be executed in actions)
// 在本地运行(此代码不会在 actions 中执行)
if (require.main === module) {
const { RELAYER_API_KEY: apiKey, RELAYER_API_SECRET: apiSecret } = process.env;
exports.handler({ apiKey, apiSecret })
.then(() => process.exit(0))
.catch(error => { console.error(error); process.exit(1); });
}
请记住在 event
对象中发送你的 action 期望的任何其他值,例如密钥或 monitor 事件。
更新代码
你可以通过 Defender 界面或使用 defender-sdk
npm 包通过 API 编程方式编辑 action 的代码。后者允许上传包含多个文件的代码包:
代码包在压缩和 base64 编码后的大小不得超过 5MB,并且必须始终在 zip 文件的根目录中包含一个 index.js 作为入口点。
|
Workflows
Workflows 允许你通过预定义的操作和场景立即检测、响应和解决威胁和攻击。你还可以进行攻击模拟并在分叉网络上测试真实场景。
Workflows 是结合了自动 actions 和事务模板的流程。可以并行运行 actions 或顺序连接 actions。可以通过 Monitor 手动或触发 Workflows。
创建 workflows 是一种无缝体验,通过一个表单引导,该表单允许你轻松地在 workflow 过程中组织 actions 。

要填充 workflow,你必须将右侧列表中的现有 actions 拖到表单上。Actions 垂直执行,这意味着先前的 actions 必须成功完成才能开始执行新行。并行 actions 同时执行。但是,如果某个 action 退出并出现错误,workflow 将完全停止。

要并行运行多个 actions,请单击“添加并行序列”并将 actions 拖到可用的并排框中。

你可以将 actions 从 workflow 中拖回以删除它们,或者单击右上角可见的减号图标以删除空步骤。右上角的“保存”按钮使用其配置和名称保存 workflow。
我们提供了一个快速入门教程来创建和使用 Workflows。在此处查看 此处! |
一个完整的例子
以下示例使用 ethers.js 和 relayer 集成来发送调用给定合约上的 execute
的交易。在发送交易之前,它会检查 canExecute
视图函数,并验证通过 webhook 接收的参数是否与本地密钥匹配。如果发送了交易,它会在响应中返回哈希,该响应将发送回 webhook 调用者。
const { ethers } = require("ethers");
const { DefenderRelaySigner, DefenderRelayProvider } = require('defender-relay-client/lib/ethers');
// Entrypoint for the action
// action 的入口点
exports.handler = async function(event) {
// Load value provided in the webhook payload (not available in schedule or sentinel invocations)
// 加载 webhook 负载中提供的值(在计划或 sentinel 调用中不可用)
const { value } = event.request.body;
// Compare it with a local secret
// 将其与本地密钥进行比较
if (value !== event.secrets.expectedValue) return;
// Initialize relayer provider and signer
// 初始化 relayer 提供程序和签名者
const provider = new DefenderRelayProvider(event);
const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });
// Create contract instance from the signer and use it to send a tx
// 从签名者创建合约实例并使用它来发送交易
const contract = new ethers.Contract(ADDRESS, ABI, signer);
if (await contract.canExecute()) {
const tx = await contract.execute();
console.log(`Called execute in ${tx.hash}`);
return { tx: tx.hash };
}
}
代码不需要等待挖掘交易。Defender 将负责监视交易并在需要时重新提交。action 只需要发送请求并退出。 |
安全注意事项
每个 action 的代码都隔离在 Defender 中,并且 actions 受到严格的访问控制限制,无法访问其他 Defender 内部基础结构。唯一的例外是 action 可能会访问其链接的 relayer,这是通过 action 服务在每次执行时注入的临时凭据协商的。尽管如此,action 只能调用 relayer 的公开方法,并且无法直接访问后备私钥或任何其他服务。
我们提供了一个快速入门教程,用于使用 Defender 为智能合约创建自动 action。在此处查看 此处! |