本指南介绍 Web3 Actions 的三个核心概念: 触发器、事件和函数。您将了解它们的工作原理,以及如何使用它们构建自己的 Web3 Actions。
组成 Web3 Action 的三个核心组件是:
- 函数(Functions): 您希望在外部事件发生时执行的自定义 JavaScript 或 TypeScript 代码。这是自动化的核心。
- 触发器(Triggers): 预定义的外部事件,您的 Web3 Action 会配置为监听它们。当事件发生时,触发器指示 Tenderly 执行您的自定义代码(函数)。
- 事件(Events,触发器类型): 预定义的外部事件,您可以通过设置触发器来监听。当事件发生时,触发器会调用您的自定义代码(函数)。
执行类型
Tenderly 中的 Web3 Actions 可以以两种模式执行: Sequential(顺序) 和 Parallel(并行)。
Sequential 执行
在顺序执行模式下,actions 按其被调用的顺序逐一执行。这是默认的执行模式。在此模式下,每个 action 会等待前一个 action 完成后才开始执行。以下是顺序执行的 action 配置示例:
our-org/our-cool-project:
runtime: v2
sources: actions
specs:
bestActionEver:
description: Does the best thing ever
+ execution_type: sequential
function: very/organized/file:bestActionEver
trigger:
...
Parallel 执行
在并行执行模式下,actions 并行执行,从而带来更高的吞吐量。在此模式下,执行顺序无法保证,并且可能与 action 存储 之间出现竞态条件。以下是并行执行的 action 配置示例:
our-org/our-cool-project:
runtime: v2
sources: actions
specs:
bestActionEver:
description: Does the best thing ever
+ execution_type: parallel
function: very/organized/file:bestActionEver
trigger:
...
Action 函数
Web3 Action 函数指的是以标准 JavaScript 或 TypeScript 函数形式编写的自定义代码,但它们必须遵守一些规则。
- 函数必须是异步的,返回
Promise<void>
- 函数接受两个参数:
context 和 event
- 函数必须是文件的具名导出
- 函数可以放在 actions root 目录下的任何文件中
了解 Web3 Actions 项目和文件结构。
在部署 Web3 Action 函数时,Tenderly 运行时会在调用期间传入以下参数:
Web3 Action 函数的执行限制为 30 秒。如果您的函数执行时间超过
30 秒,将被终止。
以下是用 TypeScript 编写的 Web3 Action 函数示例。event 参数可以是任意一种支持的触发器类型(事件)。
// File: actions/myCoolTsFile.ts
// importing ethers available in Tenderly Runtime
export const awesomeActionFunction: ActionFn = async (context: Context, event: Event) => {
// cast the event parameter to an appropriate type, based on the Trigger
// this function subscribed to.
// Of course, only one of these casts makes sense
const blockEvent = event as BlockEvent;
const periodicEvent = event as PeriodicEvent;
const transactionEvent = event as TransactionEvent;
const webhookEvent = event as WebhookEvent;
...
}
Dashboard 版 actions 可用的库
Tenderly 运行时预先打包了若干 JavaScript 库,您可以在通过 Tenderly Dashboard 创建 Web3 Actions 时导入使用。可用的库包括:
要导入这些库中的任何一个,请像在标准基于 npm 的项目中一样使用 require() 函数(不使用 ES6)。
Axios 的示例导入如下所示:
const axios = require('axios');
对基于 CLI 的 Web3 Actions 使用 npm 库
Tenderly CLI 创建的项目实际上是一个 npm 模块。您可以从 actions root 目录中安装任何 npm 包。
外部事件和触发器类型
当外部事件发生时,它 触发您的自定义代码(函数)执行。您可以在四种要监听的外部事件之间选择:
- Block 事件:在所选网络上有新区块被打包。
- Periodic 事件: 此触发器在一定时间间隔或基于 CRON 表达式的时间发生。
- Webhook 事件: HTTP 请求被发送到 webhook URL(Web3 Action 暴露 webhook)
- Transaction 事件: 在所选网络上执行了匹配给定过滤条件的事务。
在定义 Web3 Action 时,您实际上是在定义响应您项目感兴趣的外部事件的触发器。在触发器规范的上下文中,我们将使用术语 触发器类型(trigger type)。
您应为每种触发器类型编写单独的函数。不建议对不同的
触发器类型使用相同的函数。
将 Web3 Action 函数订阅到事件
除了在 JavaScript 中定义您的 action 函数之外,您还需要提供触发器配置,它告诉 Tenderly 是什么触发它,即您的函数订阅的外部事件。
通过 Tenderly Dashboard 创建 Web3 Actions 时,UI 中的创建流程将为您处理此部分。阅读 Dashboard 快速入门 指南。
在处理基于代码的 Web3 Actions 时,函数及其触发器必须在由 Tenderly CLI 生成的 tenderly.yaml 文件中定义。阅读 CLI 快速入门 指南。
示例
在下面的示例中,我们声明了一个名为 bestActionEver 的 Web3 Action,并引用从 actions/myCoolTsFile.ts 文件导出的函数 awesomeActionFunction。这是当 Web3 Action 被触发时 Tenderly 将调用的函数。执行通过 execution_type 控制,在此示例中被设置为 parallel。
account_id: ""
actions:
our-cool-org/our-cool-project:
runtime: v1
sources: actions
specs:
bestActionEver:
execution_type: parallel # or sequential
description: Does the best thing ever
function: myCoolTsFile:awesomeActionFunction
trigger: # trigger declaration start
type: ... # type
... # configuration map (external event specific)
为外部事件指定触发器
在本节中,我们将检查 trigger 声明。有关编写 tenderly.yaml 文件的更多信息,请参考 本指南。
触发器类型和相应的配置在 tenderly.yaml 文件中定义。您必须首先定义 trigger 对象,它有两个必填属性:
type 属性,指定触发器类型,可以是:periodic | webhook | block | transaction
- 一个包含所选触发器类型特定配置的对象
以下是每种触发器类型的特定配置的详细说明。
Periodic 事件
当您希望 Web3 Action 在特定时间间隔被触发时,可以使用 periodic 事件。它携带 time:调用时间。
type PeriodicEvent = {
time: Date;
};
触发器声明的类型为 periodic。可以基于间隔或 cron:
trigger:
type: periodic
periodic:
interval: 5m
interval 属性可以取以下任意值:5m | 10m | 15m | 30m | 1h | 3h | 6h | 12h | 1d
如果您需要对 Web3 Action 的执行时间进行更精细的控制,请使用基于 CRON 的 periodic 触发器:
trigger:
type: periodic
periodic:
cron: '5 */1 * * *'
cron 属性可以是任何有效的 CRON 字符串。
Webhook 事件
基于 Webhook 的 Web3 Actions 暴露一个自定义 webhook URL,使外部系统能够通过简单的 HTTP POST 请求触发它们。
对应的触发器类型包含两个属性: 调用的 time 和任意 payload(JSON 对象)。Tenderly 不会对您的负载执行任何验证或检查。
type WebhookEvent = {
time: Date;
payload: any;
};
触发器配置示例:
trigger:
type: webhook
webhook:
authenticated: true
如果 authenticated 设置为 true,您必须在请求中包含 Tenderly Access Token 作为 x-access-key 的值才能运行 Web3 Action。您可以在 Tenderly Dashboard 中的 Web3 Action 概览中找到所暴露 webhook 的 cURL。
curl -X POST \
-H "x-access-key: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
https://api.tenderly.co/api/v1/actions/7acc7db2-00d6-4872-b2d2-d9430bd8fe7b/webhook \
-d '{"foo": "bar"}'
Block 事件
当您希望监听一个或多个网络上的区块被打包时,可以使用 block 事件。您可以通过指定两次连续调用之间被打包的区块数量,让您的 Web3 Action “按块周期化”。
type BlockEvent = {
blockHash: string;
blockNumber: number;
network: string;
};
在声明 block 触发器时,您可以指定以下属性:
network:您感兴趣的 网络 ID 的单个值或列表
blocks:Web3 Action 两次连续执行之间被打包的区块数量。例如,每 100 个被打包的区块执行一次 Web3 Action。
trigger:
type: block
block:
network:
- 1
- 8453
blocks: 100
Transaction 事件
transaction 触发器类型允许您监听在链上执行的特定事务。
要监听来自智能合约的事务,智能合约必须已经过验证并添加到
Tenderly Dashboard 中的您的项目。如果不是这样,CLI 会抛出警告。
您可以以 filters 的形式指定不同的条件,以聚焦感兴趣的事务。您的自定义代码将以精确的事务负载通过 event 参数被调用。
type TransactionEvent = {
blockHash: string;
blockNumber: number;
from: string;
hash: string;
network: string;
to?: string;
logs: Array<{
address: string;
data: string;
topics: string[];
}>;
input: string;
value: string;
nonce: string;
gas: string;
gasUsed: string;
cumulativeGasUsed: string;
gasPrice: string;
gasTipCap: string;
gasFeeCap: string;
transactionHash: string;
};
监听 transaction 事件时,您还可以指定以下设置:
- 事务打包状态:
mined(事务已被打包)或 confirmed10(自包含该事务的区块以来已确认 10 个区块)
- Filters: 使用
filters 列表定义的一组涉及事务负载的条件。只有匹配 filters 的事务才会触发您的代码执行。
过滤事务
filters 列表允许您使用事务属性定义触发条件以匹配特定事务。
列表中的每个 filter 都是一个对象,其中 所有字段都是 AND 关系。filter 中的每个条件都必须为真时才能匹配。filters 列表本身是 OR 关系:只要事务满足列表中 任意一个 filter,就会匹配。
match = anyOf(
(network AND status AND from AND eventEmitted), # Filter 1
(network AND to) # Filter 2
)
以下是您可以组合以构建触发 Web3 Action 执行条件的所有可用 filter 列表。
| Filter | 描述 |
|---|
*必填 / **以下至少一个必须出现 | |
| network* | 事务来源的网络 ID |
| status | 事务状态:fail 或 success |
| from** | 事务发起者的地址 |
| to** | 事务接收者的地址 |
| eventEmitted** | 由特定合约发出的特定类型的事件 |
| logEmitted** | 由特定合约发出的日志,通过匹配原始日志条目的前缀 |
| function** | 对给定函数的直接调用。不适用于合约之间的内部调用 |
| ethBalance** | 在事务时其 ETH 余额满足某数值条件的账户或合约 |
| stateChanged** | 在事务期间其链上状态变量发生变化的合约,具有可选的值/百分比条件 |
| value | 在事务中转账的价值(以 wei 计) |
| gasUsed | 事务使用的 gas 数量(以 wei 计) |
| gasLimit | 为事务设置的 gas 上限(以 wei 计) |
| fee | 事务费用(以 wei 计) |
| contract | 事务涉及的智能合约(参见 contract filter) |
一个 trigger 必须包含 network 和以下 filter 之一:from、to、
function、eventEmitted、logEmitted、ethBalance 或 stateChanged
示例:在 Ethereum 主网或 Base 上发送到 0x236..fd62 的事务。
failedTransactionToMyContract:
execution_type: parallel
description: When sent to my contract fails
function: observingTransactions:failedAttempts
trigger:
type: transaction
transaction:
status:
- mined
filters:
# Transaction must be from Ethereum mainnet
- network: 1
# Transaction must have failed
status: fail
# Transaction must have been sent to this address
to: 0x2364259ACD20Bd2A8dEfDc628f4099302449fd62
# Or from Base
- network: 8453
# Transaction must have failed
status: fail
# Transaction must have been sent to this address
to: 0x2364259ACD20Bd2A8dEfDc628f4099302449fd62
在这种情况下,我们只对被打包且匹配以下两个 filter 中任一个的事务感兴趣:
- Filter 1:network 1(Ethereum)AND status fail AND to
0x2364...fd62
- Filter 2:network 8453(Base)AND status fail AND to
0x2364...fd62
由于我们没有按特定发件人过滤,所有发送到上述接收者的事务都会触发 Web3 Action。
按事务字段过滤事务
在常见的事务字段中,您可以按以下具有特定值的属性进行过滤。
from — 按特定发件人过滤。可以是单个地址或地址列表(OR 关系)。可选。
to — 按特定接收者过滤。可以是单个地址或地址列表(OR 关系)。可选。
status — 按事务状态过滤:success 或 fail。可以是单个值或列表(OR 关系)。可选。
network — 按网络过滤。可以是单个链 ID 或链 ID 列表(OR 关系)。必填。
contract.address — 仅过滤涉及此特定地址合约的事务。可选。
所有地址字段必须以 0x 为前缀,40 个十六进制字符(例如 0xf63c48626f874bf5604D3Ba9f4A85d5cE58f8019)。
filters:
- network: # list of networks (OR-ed)
- 1
- 8453
status: # list of statuses (OR-ed)
- success
- fail
from: # list of sender addresses (OR-ed)
- 0x7ebB3Dca1C281b23D5B73175f10cA5A0a309B01F
- 0xD3a02149A236b2547Cc3C897Fb41C1a962f881AE
to: # list of recipient addresses (OR-ed)
- 0x003b3625cDcb5958E9709F4Ba8E340Cb0783DeaE
- 0x26997bd8473E0Dd0b37eB1711B7c1eE2354d78e4
您还可以使用关系运算符查询与交换价值和 gas 相关的数值。以下所有值均以 wei 为单位:
value
gasUsed
gasLimit
fee
数值字段接受单个比较对象或比较对象列表。当提供列表时,比较是 OR 关系。在单个比较对象内,所有运算符都是 AND 关系。
您可以使用任何常见的比较运算符:eq、gte、gt、lt、lte。您还可以使用 not 布尔标志对比较进行取反。
| 运算符 | 含义 |
|---|
eq | 等于 |
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
not | 布尔值。设为 true 以取反比较(例如,当值 不 在范围内时匹配) |
以下是一个展示使用事务负载中标量值的 filter 示例。
filters:
- network: 1
to: 0x003b3625cDcb5958E9709F4Ba8E340Cb0783DeaE
# Single comparison object (operators AND-ed within)
value:
gte: 100
lte: 1000
# List of comparison objects (OR-ed between entries)
gasLimit:
- lt: 100
- gt: 1000
gasUsed:
eq: 9999
fee:
- lte: 100
- gte: 1000
使用 not 标志取反比较:
filters:
- network: 1
to: 0x003b3625cDcb5958E9709F4Ba8E340Cb0783DeaE
# Matches when value is NOT between 100 and 1000
value:
gte: 100
lte: 1000
not: true
使用发出的 EVM 事件过滤事务
将 Web3 Actions 配置为监听由事务执行发出的 EVM 事件,允许您对这些事件记录的重要变化作出响应。您可以通过使用 eventEmitted 运算符实现这一点,它支持以下嵌套属性:
contract(必填):指定事件的来源。
contract.address:发出该事件的合约的地址。
contract.invocation:控制合约如何被调用:direct、internal 或 any(默认)。有关详情,请参见 contract filter。
name:事件的名称(需要已验证的合约)。
id:事件签名的 topic 哈希(作为 name 的替代方案)。对于 ABI 不可用的未验证合约很有用。例如,Transfer(address,address,uint256) 事件的 topic 哈希是 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef。
not:布尔值。设为 true 以对整个事件匹配(包括参数)取反(当未使用指定参数发出该事件时触发)。
parameters:解码后事件参数值的条件列表。所有条件都是 AND 关系:每个条件都必须匹配。每个条目支持:
name(必填):事件参数的名称。
string:字符串比较。接受用于精确匹配的普通字符串,或包含 exact 和 not 字段的对象。有关详情,请参见 StringComparison。
int:整数比较。支持与数值 filter 相同的运算符(eq、gt、gte、lt、lte)。单个 int 块内的所有运算符都是 AND 关系。使用 not 标志对组合条件取反。有关详情,请参见 IntComparison。
单个 eventEmitted 条目的 parameters 内没有 OR 关系。所有条件都是 AND 关系。
要对同一参数使用 OR 逻辑,请使用多个 eventEmitted 条目(它们在外层列表中是 OR 关系)。
要将 eventEmitted filter 与 name 或 parameters 结合使用,合约必须已验证并添加到
Tenderly Dashboard 中的项目。对未验证的合约使用 id(topic 哈希)。
有关字段的完整参考,请参见 EventFilter。
以下是一个 Web3 Action 示例,当智能合约在 Sepolia 上的 0x418d..9d45 地址执行时发出 TxSubmission 或 TxConfirmation 事件之一时将被调用。
event-emitted-filter.yaml
txSubmittedOrConfirmed:
description: When any of TxSubmission and TxConfirmation is emitted
execution_type: parallel
function: myCoolJsFile:myCoolFunction
trigger:
type: transaction
transaction:
status:
- mined
filters:
- network: 11155111
eventEmitted:
contract:
address: 0x418ebb95eaa40c119408143056cad984c6129d45
name: TxSubmission
- network: 11155111
eventEmitted:
contract:
address: 0x418ebb95eaa40c119408143056cad984c6129d45
name: TxConfirmation
以下是按解码后事件参数过滤的示例。所有参数条件都是 AND 关系:
filters:
- network: 8453
eventEmitted:
contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer
parameters:
- name: from
string: "0x8F362C3dd5301Ce1b43ea3A278E3f12c1807C271"
- name: value
int:
gte: 1000000 # >= 1 USDC (6 decimals)
对参数使用带取反的数值范围:
filters:
- network: 1
eventEmitted:
contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer
parameters:
- name: value
int:
gte: 100
lte: 1000
not: true # matches when value < 100 OR value > 1000
对参数使用 OR 逻辑,请使用多个 eventEmitted 条目:
filters:
- network: 1
eventEmitted:
# Entry 1: value < 100
- contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer
parameters:
- name: value
int:
lt: 100
# Entry 2: value > 1000 (OR-ed with entry 1)
- contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer
parameters:
- name: value
int:
gt: 1000
在事件本身上使用 not 以取反整个匹配:
filters:
- network: 1
eventEmitted:
contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer
parameters:
- name: from
string: "0x0000000000000000000000000000000000000000"
not: true # matches when no Transfer event from the zero address is found
按发出的日志过滤事务
监听 EVM 事件的另一种方式是按日志 topic 查询。
logEmitted filter 可以是单个对象或列表(条目是 OR 关系)。它支持以下属性:
startsWith(必填):与事务日志 topic 匹配的 topic 值列表。每个条目通过精确相等(不区分大小写)与相应的 topic 进行比较。实际上这些是完整的 32 字节 topic 哈希(以 0x 为前缀,64 个十六进制字符),但该字段接受任何有效的十六进制字符串。
contract.address(可选):日志的来源。指定时,只匹配来自此合约的日志。
matchAny:布尔值。当 true 时,匹配 startsWith 中 任意一个 topic 就足够了。当 false(默认)时,所有 topic 都必须匹配。
not:布尔值。设为 true 以取反整个日志匹配(当未发出该日志时触发)。
使用 logEmitted 使您能够以其原始形式使用事件 topic 前缀来过滤事件。
如果您在未验证的合约上设置 Web3 Action,这将非常有用。
有关字段的完整参考,请参见 LogFilter。
以下是一个 Web3 Action 示例,当事务包含以给定前缀开头的日志条目时将被调用。
txEmittedSomeLogs:
description: When particular logs are emitted
function: observingTransactions:someLogsEmittedAction
execution_type: parallel
trigger:
type: transaction
transaction:
status:
- mined
filters:
- network: 11155111
logEmitted:
# Transaction must have emitted a log entry
contract:
address: 0x418ebb95eaa40c119408143056cad984c6129d45
startsWith:
# and topics of the log entry must start with either one of these
- 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
- 0x0000000000000000000000000000000000000000000000000000000000000000
过滤涉及特定合约的事务
要过滤在执行期间调用特定合约的事务,您可以使用可选的 contract filter。
contract filter 支持以下属性:
address:合约的地址。
invocation:控制合约如何被调用。可能的值:
direct:合约被事务发送者(EOA)直接调用。仅关注对该合约的顶层调用时使用。
internal:合约在执行过程中被另一个合约内部调用(例如通过 CALL、DELEGATECALL 或 STATICCALL opcode)。用于检测您的合约作为多合约交互一部分被调用时的情况。
any:匹配 direct 和 internal 两种调用。这是默认值。
invocation 字段在使用 contract 对象的任何位置都可用,包括 eventEmitted filter 内部。例如,您可以在 eventEmitted 合约上使用 invocation: internal,只匹配在内部调用期间发出的事件。有关 schema,请参见 Contract。
logEmitted 不支持 invocation。仅使用 contract.address 限制日志匹配;invocation 类型被忽略。
在下面的示例中,第一个 filter 匹配 Sepolia 上发送到 0x2364...fd62 的任何成功事务。第二个 filter 更严格:只有还涉及对合约 0xad88...80d6 的内部调用的事务才会触发 Web3 Action。
contractInvocationAction:
description: When my contract is called directly or internally
execution_type: parallel
function: observingTransactions:contractCalls
trigger:
type: transaction
transaction:
status:
- mined
filters:
# Any successful transaction sent to this address
- network: 11155111
status: success
to: 0x2364259ACD20Bd2A8dEfDc628f4099302449fd62
# Only transactions that internally call this contract
- network: 11155111
status: success
to: 0x2364259ACD20Bd2A8dEfDc628f4099302449fd62
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
invocation: internal
按函数调用过滤事务
要过滤直接调用特定函数的事务,您可以使用可选的 function filter。它需要合约的地址 contract.address 以及函数的 name 或 signature。
function filter 支持以下属性:
contract(必填):指定目标合约。
contract.address:被调用合约的地址。
contract.invocation:控制函数必须如何被调用:any(默认,包括 direct 和 internal)、direct(仅由 EOA 发起)或 internal(仅子调用)。有关详情,请参见 contract filter。
name:函数的名称(需要已验证的合约)。
signature:4 字节函数选择器(例如 0xa9059cbb)。对于 ABI 不可用的未验证合约很有用。
not:布尔值。设为 true 以对整个函数匹配(包括参数)取反(当未使用指定参数调用该函数时触发)。
parameters:解码后函数输入参数值的条件列表。所有条件都是 AND 关系:每个条件都必须匹配。每个条目支持:
name(必填):函数参数的名称。
string:字符串比较。接受用于精确匹配的普通字符串,或包含 exact 和 not 字段的对象。有关详情,请参见 StringComparison。
int:整数比较。支持与数值 filter 相同的运算符(eq、gt、gte、lt、lte)。单个 int 块内的所有运算符都是 AND 关系。使用 not 标志对组合条件取反。有关详情,请参见 IntComparison。
单个 function 条目的 parameters 内没有 OR 关系。所有条件都是 AND 关系。
要对同一参数使用 OR 逻辑,请使用多个 function 条目(它们在外层列表中是 OR 关系)。
要将 function filter 与 name 或 parameters 结合使用,合约必须已验证并添加到
Tenderly Dashboard 中的项目。对未验证的合约使用 signature(4 字节选择器)。
您可以将 function 指定为单个对象或列表。当提供列表时,条目是 OR 关系。如果列表中 任何一个 函数被调用,filter 就匹配。有关字段的完整参考,请参见 FunctionFilter。
以下是一个 Web3 Action 触发器示例,响应对部署在 0xad88...80d6 的合约 verySpecialFunction 的任何调用(direct 或 internal)。
failedTransactionToMyContract:
description: When sent to my contract fails
execution_type: parallel
function: observingTransactions:failedAttempts
trigger:
type: transaction
transaction:
status:
- mined
filters:
# Transaction must be from Ethereum mainnet
- network: 1
# Transaction must have failed
status: fail
function:
# the function verySpecialFunction must have been called (direct or internal)
name: verySpecialFunction
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
您还可以按函数选择器匹配,并使用多个 function 条目:
filters:
- network: 1
function:
# Match by 4-byte selector (useful for unverified contracts)
- signature: "0xa9059cbb"
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
# Match by name (requires verified contract)
- name: approve
not: true # negate: trigger when approve is NOT called
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
以下是按解码后函数输入参数过滤的示例。所有参数条件都是 AND 关系:
filters:
- network: 1
function:
name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
parameters:
- name: to
string: "0x8F362C3dd5301Ce1b43ea3A278E3f12c1807C271"
- name: amount
int:
gte: 1000000 # >= 1 USDC (6 decimals)
字符串取反,当接收者 不是 特定地址时触发:
function-negated-string.yaml
filters:
- network: 1
function:
name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
parameters:
- name: to
string:
exact: "0xa59a97Ab934aE02A1E0561ea0f4F6C275Fc148B4"
not: true # matches when 'to' is NOT this address
对参数使用带取反的数值范围:
function-negated-parameter.yaml
filters:
- network: 1
function:
name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
parameters:
- name: amount
int:
gte: 100
lte: 1000
not: true # matches when amount < 100 OR amount > 1000
对参数使用 OR 逻辑,请使用多个 function 条目:
function-parameter-or.yaml
filters:
- network: 1
function:
# Entry 1: amount < 100
- name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
parameters:
- name: amount
int:
lt: 100
# Entry 2: amount > 1000 (OR-ed with entry 1)
- name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
parameters:
- name: amount
int:
gt: 1000
要仅匹配内部调用,例如由路由器或闪电贷提供者触发的 transfer 而不是用户直接调用,设置 invocation: internal。此示例仅在另一个合约在 USDC 上内部调用 transfer 至少 1,000 USDC 时触发:
function-internal-with-params.yaml
filters:
- network: 1
function:
name: transfer
contract:
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
invocation: internal # sub-calls only — excludes direct EOA calls
parameters:
- name: amount
int:
gte: 1000000000 # >= 1,000 USDC (6 decimals)
按 ETH 余额过滤事务
ethBalance filter 在事务期间账户或合约的 ETH 余额满足数值条件时触发您的 Web3 Action。这对于监控大型鲸鱼动态、协议金库阈值或钱包充值事件很有用。
filter 支持以下属性:
address(必填):要监控的账户或合约地址。
balanceCmp(必填):与事务时余额的数值比较。使用 BigIntComparison 运算符(eq、gt、gte、lt、lte)。值以 wei 为单位,可以以十进制字符串(例如 "1000000000000000000")或 0x 前缀的十六进制(例如 "0xde0b6b3a7640000")提供。
not(可选):设为 true 以取反整个匹配。
您可以提供单个 ethBalance 对象或列表,条目是 OR 关系。
有关字段的完整参考,请参见 EthBalanceFilter。
示例:当钱包余额至少达到 1 ETH 时触发:
balanceWatcher:
description: Alert when wallet reaches 1 ETH
function: watchers:onBalanceReached
trigger:
type: transaction
transaction:
status:
- mined
filters:
- network: 1
ethBalance:
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" # vitalik.eth
balanceCmp:
gte: "1000000000000000000" # 1 ETH in wei
示例:在一个 filter 中监控多个账户(OR 关系):
filters:
- network: 1
ethBalance:
- address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" # vitalik.eth
balanceCmp:
gte: "1000000000000000000"
- address: "0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8" # Binance cold wallet
balanceCmp:
lte: "500000000000000000" # below 0.5 ETH
示例:余额在某个范围内:
filters:
- network: 1
ethBalance:
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" # vitalik.eth
balanceCmp:
gte: "1000000000000000000" # >= 1 ETH
lte: "2000000000000000000" # <= 2 ETH
示例:当钱包余额 不 低于 1 ETH 时触发(即取反条件):
filters:
- network: 1
ethBalance:
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" # vitalik.eth
balanceCmp:
lt: "1000000000000000000" # < 1 ETH
not: true # negate: trigger when balance is NOT below 1 ETH
按合约状态变化过滤事务
stateChanged filter 在事务期间合约状态变量发生变化时触发您的 Web3 Action。您可以在以下任何组合上匹配:变量是否发生变化、特定的新值、百分比变化或原始存储槽键。
filter 支持以下属性:
address(除非 matchAny: true,否则必填):要监视的合约地址。
matchAny(可选):当 true 时,匹配事务中 任何 合约的状态变化,address 可以省略。对协议范围的监控很有用。
params(可选):状态变量条件列表。所有条目都是 AND 关系。省略时,filter 匹配指定地址上的任何状态变化。
not(可选):设为 true 以取反整个匹配。
params 中的每个条目需要:
name(必填):状态变量的名称。
- 至少一个条件:
change: true:当变量的值发生变化时匹配。
valueCmp(BigIntComparison),当新值满足数值条件时匹配。
percentageCmp(BigIntComparison),匹配按 (newValue - oldValue) * 100 / oldValue 计算的 带符号 百分比变化。正值表示增加,负值表示减少。例如,gte: "10" 在增加 10%+ 时触发;lte: "-10" 在减少 10%+ 时触发。注意:当 oldValue 为 0 时,百分比始终为 0,请改用 change: true 或 valueCmp。
storageSlotKey:按原始存储槽键匹配(对未验证的合约很有用)。
有关字段的完整参考,请参见 StateChangedFilter。
要使用基于 name 的状态变量匹配,合约必须已验证并添加到您在 Tenderly Dashboard 的项目中。
示例:当 totalSupply 发生任何变化时触发:
stateWatcher:
description: Alert on any totalSupply change
function: watchers:onStateChange
trigger:
type: transaction
transaction:
status:
- mined
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
- name: totalSupply
change: true
示例:当 totalSupply 达到阈值时触发:
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
- name: totalSupply
valueCmp:
gte: "1000000000000000000000" # >= 1000 DAI (18 decimals)
示例:当 totalSupply 增加 50% 或以上时触发:
state-changed-percentage-increase.yaml
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
- name: totalSupply
percentageCmp:
gte: "50" # positive = increase; fires when totalSupply grew by >= 50%
示例:当 totalSupply 减少 10% 或以上时触发:
state-changed-percentage-decrease.yaml
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
- name: totalSupply
percentageCmp:
lte: "-10" # negative = decrease; fires when totalSupply dropped by >= 10%
示例:多个参数条件 AND 关系(totalSupply 发生变化 AND 新值高于某个下限):
state-changed-multi-params.yaml
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
- name: totalSupply
change: true
- name: totalSupply
valueCmp:
gte: "1000000000000000000000000" # new totalSupply still >= 1 million DAI
示例:使用原始存储槽键监视特定映射槽(例如 balances[bob]):
对于映射,没有按条目命名的变量,请对您关心的键使用 storageSlotKey 和 keccak256 派生的槽。对于 mapping(address => uint256) balances 在存储槽 N 和键 addr 上的槽是 keccak256(abi.encode(addr, N))。
filters:
- network: 1
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
params:
# slot = keccak256(abi.encode(bob_address, mapping_slot_index))
# compute this off-chain with ethers.js: ethers.solidityPackedKeccak256(["address", "uint256"], [bobAddress, slotIndex])
- storageSlotKey: "0x<precomputed-slot-hash>"
change: true
storageSlotKey 方法适用于未验证的合约和任何存储布局,它不要求合约被验证或添加到您的 Tenderly 项目中。
示例:使用 matchAny 监视事务中的任何合约:
state-changed-match-any.yaml
filters:
- network: 1
function:
name: liquidate
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
stateChanged:
matchAny: true
params:
- name: collateralBalance
change: true
stateChanged 和 ethBalance 可以在同一 filter 对象中与任何其他 filter 结合,所有字段都是 AND 关系。以下示例展示了常见的实际组合。
示例:mint() 被调用 AND totalSupply 发生变化(确认该调用产生了链上效果):
state-changed-with-function.yaml
filters:
- network: 8453
function:
name: mint
contract:
address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
stateChanged:
address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
params:
- name: totalSupply
change: true
示例:Transfer 事件被发出 AND paused 状态发生变化(在转账期间所有权或暂停标志被切换):
state-changed-with-event.yaml
filters:
- network: 8453
eventEmitted:
contract:
address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
name: Transfer
stateChanged:
address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
params:
- name: paused
change: true
示例:对合约的任何调用 AND 调用者的 ETH 余额高于阈值:
filters:
- network: 8453
to: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
ethBalance:
address: "0x4786f86f7d9d1467928b3518f123ea4fb65f1500"
balanceCmp:
gte: "500000000000000" # caller has >= 0.0005 ETH
示例:当 togglePause() 被调用但 balances 没有 变化时触发(即事务仅影响了 paused 标志):
filters:
- network: 8453
to: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
stateChanged:
address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
params:
- name: balances
change: true
not: true # negate: trigger when balances did NOT change
完整的 transaction 触发器参考
以下带注释的 YAML 块在一处展示了 transaction 触发器的每个可用字段。在构建您的触发器配置时,请将其用作快速参考。有关类型定义,请参见下面的 Trigger YAML schema 参考。
trigger:
type: transaction
transaction:
status:
- mined # or confirmed10
# At least one filter must match (OR logic).
# Within a filter, all fields are AND-ed.
filters:
- network: 1 # mandatory (single or list)
status: success # fail | success (single or list)
from: "0xDB6Fd484cFA46eeB73c71165C6C004B50309BbE1" # single or list of addresses
to: "0x2364259ACD20Bd2A8dEfDc628f4099302449fd62" # single or list of addresses
# --- Contract filter ---
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
invocation: any # any | direct | internal
# --- Numeric comparisons (all in wei) ---
# Each can be a single object or a list (OR-ed between entries).
# Within a single object, all operators are AND-ed.
value:
gte: 1000000000000000000 # >= 1 ETH
not: false # set true to negate
gasUsed:
eq: 21000
gasLimit: # list form (OR-ed)
- lt: 100
- gt: 1000
fee:
- lte: 100
- gte: 1000
# --- Event filter (single object or list, OR-ed) ---
eventEmitted:
- contract:
address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
name: Transfer # match by event name (verified contract)
# id: "0xddf252..." # alternative: match by topic hash
not: false # negate entire event match
parameters: # filter by decoded event arguments (AND-ed)
- name: from
string: "0x8F362C3dd5301Ce1b43ea3A278E3f12c1807C271"
- name: value
int:
gte: 1000000 # >= 1 USDC (6 decimals)
# --- Log filter (single object or list, OR-ed) ---
logEmitted:
- startsWith:
- "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
contract:
address: 0x418ebb95eaa40c119408143056cad984c6129d45
matchAny: false # true = match any single topic
not: false # negate entire log match
# --- Function filter (single object or list, OR-ed) ---
function:
- name: transfer # match by name (verified contract)
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
invocation: any # any (default) | direct | internal
not: false # negate entire function + parameters match
parameters: # filter by decoded function arguments (AND-ed)
- name: to
string: "0x8F362C3dd5301Ce1b43ea3A278E3f12c1807C271" # short form
- name: from
string: # long form (with negation)
exact: "0xa59a97Ab934aE02A1E0561ea0f4F6C275Fc148B4"
not: true # negate this string match
- name: amount
int:
gte: 1000000 # >= 1 USDC (6 decimals)
# name and signature are mutually exclusive — each entry uses one or the other, not both
- signature: "0xa9059cbb" # match by 4-byte selector (0x + 8 hex chars)
not: true # negate this function match
contract:
address: 0xad881d3d06c7f168715b84b54f9d3e1ff27b80d6
# --- ETH balance filter (single object or list, OR-ed) ---
ethBalance:
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" # vitalik.eth
balanceCmp:
gte: "1000000000000000000" # >= 1 ETH in wei (decimal or 0x-prefixed hex)
not: false # negate the balance match
# --- State changed filter (single object or list, OR-ed) ---
stateChanged:
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" # DAI on mainnet
matchAny: false # true = match any contract in the transaction
not: false # negate the entire state change match
params: # all entries AND-ed
- name: totalSupply # state variable name (verified contract)
change: true # matches when the value changed at all
- name: totalSupply
valueCmp: # BigIntComparison on the new value
gte: "1000"
- name: totalSupply
percentageCmp: # signed: (newVal-oldVal)*100/oldVal
gte: "50" # >= 50 means increased by >= 50%
# use lte: "-50" to match a decrease of >= 50%
Trigger YAML schema 参考
下面的 schema 描述了触发器配置接受的每个字段的结构和类型。在构建您的 tenderly.yaml 时,请与上面的 带注释示例 一起使用。
约定:? = 可选,| = 之一,[] = 列表。标记为 “single or list” 的字段接受标量值或 YAML 列表。
所有触发器类型
trigger:
type: periodic | webhook | block | transaction
periodic: # when type = periodic
interval: string? # "5m" | "10m" | "15m" | "30m" | "1h" | "3h" | "6h" | "12h" | "1d"
cron: string? # standard cron expression (mutually exclusive with interval)
webhook: # when type = webhook
authenticated: bool? # default: true
block: # when type = block
network: Network # single chain ID or list of chain IDs
blocks: int # run every N blocks (must be > 0)
transaction: # when type = transaction
status: TransactionStatus[] # "mined" | "confirmed10"
filters: TransactionFilter[] # at least one filter required
TransactionFilter
# All fields within a filter are AND-ed.
# Multiple filters in the list are OR-ed.
TransactionFilter:
network: Network # required. single chain ID or list (OR-ed)
status: Status # optional. single or list: "success" | "fail" (OR-ed)
from: Address # optional. single or list of addresses (OR-ed)
to: Address # optional. single or list of addresses (OR-ed)
value: IntComparison # optional. single object or list (OR-ed)
gasUsed: IntComparison # optional. single object or list (OR-ed)
gasLimit: IntComparison # optional. single object or list (OR-ed)
fee: IntComparison # optional. single object or list (OR-ed)
contract: Contract # optional. shared default — applied to function/eventEmitted that omit their own contract
function: FunctionFilter # optional. single object or list (OR-ed)
eventEmitted: EventFilter # optional. single object or list (OR-ed)
logEmitted: LogFilter # optional. single object or list (OR-ed)
ethBalance: EthBalanceFilter # optional. single object or list (OR-ed)
stateChanged: StateChangedFilter # optional. single object or list (OR-ed)
# constraint: at least one of from, to, function, eventEmitted, logEmitted, ethBalance, or stateChanged is required
每个 filter 中必须至少存在 from、to、function、eventEmitted、logEmitted、ethBalance 或 stateChanged 之一。
顶层的 contract 字段充当 filter 的共享默认值。它会自动应用于任何未指定自身合约的 function 或 eventEmitted 条目,让您避免重复相同的地址。它 不适用 于 logEmitted。
共享类型
Address: string # 0x-prefixed, 40 hex characters (case-insensitive)
Network: int | int[] # chain ID(s)
Status: string | string[] # "success" | "fail"
Contract:
address: Address # required
invocation: string? # "any" (default) | "direct" | "internal"
IntComparison: # single object or list of objects (OR-ed)
eq: int? # equal to
gt: int? # greater than
gte: int? # greater than or equal
lt: int? # less than
lte: int? # less than or equal
not: bool? # negate the combined condition
BigIntComparison: # used by ethBalance and stateChanged filters
# values are decimal strings ("1000000000000000000") or 0x-prefixed hex ("0xde0b6b3a7640000")
# for percentageCmp: signed value, (newVal-oldVal)*100/oldVal — positive=increase, negative=decrease
eq: string? # equal to
gt: string? # greater than
gte: string? # greater than or equal
lt: string? # less than
lte: string? # less than or equal
not: bool? # negate the combined condition
# at least one of eq, gt, gte, lt, lte must be set
StringComparison: # accepts a plain string OR an object
# Short form: string: "0xdead..." → exact match, not = false
# Long form: string: { exact: "0xdead...", not: true }
exact: string? # exact match (case-insensitive)
not: bool? # negate the match
FunctionFilter
FunctionFilter: # single object or list (OR-ed)
contract: Contract # required
name: string? # function name (requires verified contract)
signature: string? # 4-byte selector, 0x-prefixed, 8 hex chars
not: bool? # negate entire function + parameters match
parameters: ParameterCondition[]?
# note: name and signature are mutually exclusive (use one or the other)
# note: parameters requires a verified contract (name must be set, not signature)
ParameterCondition: # all conditions in the list are AND-ed
name: string # required. function argument name
string: StringComparison? # string comparison (plain string or { exact, not })
int: IntComparison? # numeric comparison
EventFilter (eventEmitted)
EventFilter: # single object or list (OR-ed)
contract: Contract # required
name: string? # event name (requires verified contract)
id: string? # topic hash, 0x-prefixed, 64 hex chars
not: bool? # negate entire event + parameters match
parameters: ParameterCondition[]?
# note: name and id are mutually exclusive (use one or the other)
# note: see ParameterCondition definition under FunctionFilter above
LogFilter (logEmitted)
LogFilter: # single object or list (OR-ed)
startsWith: string[] # required. hex strings (typically 0x + 64 hex chars for full topic hashes)
contract: Contract? # optional. restrict to logs from this contract address
# note: invocation is not supported for logEmitted; only contract.address is used
matchAny: bool? # true = match any topic; false (default) = match all
not: bool? # negate entire log match
EthBalanceFilter (ethBalance)
EthBalanceFilter: # single object or list (OR-ed)
address: Address # required. account or contract address to monitor
balanceCmp: BigIntComparison # required. condition on the ETH balance in wei
not: bool? # negate the balance match
StateChangedFilter (stateChanged)
StateChangedFilter: # single object or list (OR-ed)
address: Address? # required unless matchAny = true
matchAny: bool? # true = match state changes on any contract in the transaction
params: StateChangedParamCondition[]? # optional. when omitted, matches any state change on address
not: bool? # negate the entire state change match
StateChangedParamCondition: # all entries in the params list are AND-ed
name: string # required. state variable name (verified contract)
# at least one of the following must be set:
change: bool? # true = matches when the variable changed at all
valueCmp: BigIntComparison? # condition on the new value
percentageCmp: BigIntComparison? # signed: (newVal-oldVal)*100/oldVal
# gte: "10" → increased by >= 10%
# lte: "-10" → decreased by >= 10%
# returns 0 when oldVal is 0; use valueCmp or change: true instead
storageSlotKey: string? # raw storage slot key (useful for unverified contracts)