Перейти к основному содержанию
В этом руководстве представлены три основных понятия Web3 Actions: триггеры, события и функции. Вы узнаете, как они работают и как использовать их для создания собственных Web3 Actions. Три основных компонента, из которых состоит Web3 Action:
  • Функции: пользовательский код на JavaScript или TypeScript, который вы хотите выполнить при возникновении внешнего события. Это ядро вашей автоматизации.
  • Триггеры: предопределённые внешние события, которые ваш Web3 Action настроен отслеживать. Когда событие происходит, триггер указывает Tenderly выполнить ваш пользовательский код (функцию).
  • События (типы триггеров): предопределённые внешние события, которые вы можете отслеживать, установив триггер. Когда событие происходит, триггер вызовет ваш пользовательский код (функции).

Типы выполнения

Web3 Actions в Tenderly могут выполняться в двух режимах: Sequential (последовательный) и Parallel (параллельный).
Web3 Action Execution Type step in the Web3 Action UI Builder

Последовательное выполнение (Sequential)

При последовательном выполнении actions запускаются один за другим в том порядке, в котором они были вызваны. Это режим по умолчанию. В этом режиме каждый action ждёт, пока предыдущий завершит выполнение, прежде чем начать своё. Вот пример конфигурации action для последовательного выполнения:
example
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 storage. Вот пример конфигурации action для параллельного выполнения:
example
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 (actions root)
Узнайте о структуре проекта и файлов Web3 Actions. Когда функция Web3 Action развёрнута, runtime Tenderly передаст следующие аргументы при её вызове:
  • Параметр context, дающий доступ к Storage и Secrets.
  • Параметр event — объект с информацией, отвечающей на вопрос «что только что произошло?». Этот параметр содержит данные, специфичные для типа триггера, на который подписана функция Web3 Action. Раздел Задание триггеров для внешних событий подробно рассматривает внешние события.
Выполнение функций Web3 Action ограничено 30 секундами. Если ваша функция выполняется дольше 30 секунд, она будет прервана.
Вот пример функции Web3 Action, написанной на TypeScript. Параметр event может быть любым из поддерживаемых типов триггеров (событий).
example.ts
// 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;
	...
}

Доступные библиотеки для actions на базе dashboard

Runtime Tenderly поставляется с несколькими предустановленными JavaScript-библиотеками, которые вы можете импортировать при создании Web3 Actions через Tenderly Dashboard. Доступные библиотеки: Чтобы импортировать любую из этих библиотек, используйте функцию require() так же, как в стандартном npm-проекте (без ES6). Пример импорта Axios выглядит так:
example.jsx
const axios = require('axios');

Использование npm-библиотек для Web3 Actions на базе CLI

Проект, создаваемый Tenderly CLI, по сути является npm-модулем. Вы можете установить любой npm-пакет в каталоге корня actions.

Внешние события и типы триггеров

Когда происходит внешнее событие, оно запускает выполнение вашего пользовательского кода (функций). Вы можете выбрать одно из четырёх внешних событий для отслеживания:
  • Block event: блок заминирован в выбранной сети.
  • Periodic event: этот триггер срабатывает при прохождении определённых интервалов времени или на основе CRON-выражений.
  • Webhook event: HTTP-запрос отправлен на URL webhook (Web3 Action предоставляет webhook)
  • Transaction event: транзакция, соответствующая заданным критериям фильтра, выполняется в выбранной сети.
При определении Web3 Action вы фактически задаёте триггеры, реагирующие на важные для вашего проекта внешние события. В контексте спецификации триггеров мы будем использовать термин тип триггера.
Вы должны писать отдельные функции для каждого типа триггера. Использовать одну функцию для разных типов триггеров не рекомендуется.

Подписка функций Web3 Action на события

Помимо определения функции action на JavaScript, вам также нужно предоставить конфигурацию триггера, которая сообщает Tenderly, что его запускает — внешнее событие, на которое подписана ваша функция. При создании Web3 Actions через Tenderly Dashboard процесс создания в UI позаботится об этом за вас. Прочитайте руководство Быстрый старт через Dashboard. При работе с Web3 Actions на основе кода функции и их триггеры должны быть определены в файле tenderly.yaml, генерируемом Tenderly CLI. Прочитайте руководство Быстрый старт через CLI. Пример В примере ниже мы объявляем Web3 Action под именем bestActionEver и ссылаемся на функцию awesomeActionFunction, экспортированную из файла actions/myCoolTsFile.ts. Это функция, которую Tenderly вызовет при срабатывании Web3 Action. Выполнение контролируется через execution_type, в этом случае оно установлено в parallel.
tenderly.yaml
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 event

Periodic event используется, когда вы хотите, чтобы ваш Web3 Action срабатывал через определённые интервалы времени. Оно несёт в себе time: время вызова.
example.ts
type PeriodicEvent = {
  time: Date;
};
Объявление триггера имеет тип periodic. Оно может быть основано на интервале или на cron:
trigger.yaml
trigger:
  type: periodic
  periodic:
    interval: 5m
Свойство interval может принимать любое из следующих значений: 5m | 10m | 15m | 30m | 1h | 3h | 6h | 12h | 1d Используйте периодический триггер на основе CRON, если вам нужен более гранулярный контроль над тем, когда выполняется ваш Web3 Action:
trigger.yaml
trigger:
  type: periodic
  periodic:
    cron: '5 */1 * * *'
Свойство cron может быть любой валидной CRON-строкой.

Webhook event

Web3 Actions на основе webhook предоставляют пользовательский URL webhook, что позволяет запускать их из внешних систем с помощью простого HTTP POST запроса. Соответствующий тип триггера содержит два свойства: time вызова и любую payload (JSON-объект). Tenderly не выполняет никаких проверок или инспекций вашей полезной нагрузки.
example.ts
type WebhookEvent = {
  time: Date;
  payload: any;
};
Пример конфигурации триггера:
webhook-trigger.yaml
trigger:
  type: webhook
  webhook:
    authenticated: true
Если authenticated установлено в true, вы должны включить Tenderly Access Token в свой запрос как значение x-access-key, чтобы иметь возможность запустить Web3 Action. cURL для предоставленного webhook можно найти в обзоре Web3 Action в Tenderly Dashboard.
example
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 event

Block event используется, когда вы хотите отслеживать майнинг блоков в одной или нескольких сетях. Вы можете сделать ваш Web3 Action «блочно-периодическим», указав количество заминированных блоков между двумя последовательными вызовами.
example.ts
type BlockEvent = {
  blockHash: string;
  blockNumber: number;
  network: string;
};
При объявлении блочного триггера вы можете указать следующие свойства:
  • network: одно значение или список ID сетей, которые вас интересуют
  • blocks: количество заминированных блоков между двумя последовательными выполнениями Web3 Action. Например, выполнять Web3 Action на каждом 100-м заминированном блоке.
block-trigger.yaml
trigger:
  type: block
  block:
    network:
      - 1
      - 8453
    blocks: 100

Transaction event

Тип триггера transaction позволяет отслеживать конкретные транзакции по мере их выполнения в цепочке.
Чтобы отслеживать транзакции со смарт-контракта, смарт-контракт должен быть верифицирован и добавлен в ваш проект в Tenderly Dashboard. CLI выдаст предупреждение, если это не так.
Вы можете задать разные условия в виде фильтров, чтобы точно определить интересующие вас транзакции. Ваш пользовательский код будет вызван с той самой полезной нагрузкой транзакции, переданной через параметр event.
example.ts
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;
};
При отслеживании событий транзакций вы также можете указать следующие настройки:
  • Статус майнинга транзакции: mined (транзакция заминирована) или confirmed10 (подтверждено 10 блоков после блока, содержащего транзакцию)
  • Фильтры: список условий, включающих полезную нагрузку транзакции, с использованием списка filters. Только транзакции, соответствующие фильтрам, запустят выполнение вашего кода.

Фильтрация транзакций

Список filters позволяет задавать критерии срабатывания с использованием свойств транзакции для сопоставления конкретных транзакций. Каждый фильтр в списке — это объект, где все поля объединены логикой AND. Каждое условие фильтра должно быть истинным, чтобы он совпал. Сам список filters объединён логикой OR: транзакция совпадает, если удовлетворяет любому одному фильтру из списка.
match = anyOf(
  (network AND status AND from AND eventEmitted),   # Filter 1
  (network AND to)                                  # Filter 2
)
Ниже приведён список всех доступных фильтров, которые можно комбинировать для построения критериев транзакций, запускающих выполнение вашего Web3 Action.
ФильтрОписание

*обязательный / **должен присутствовать хотя бы один из этих

network*ID сети, из которой отправлена транзакция
statusСтатус транзакции: fail или success
from**Адрес отправителя транзакции
to**Адрес получателя транзакции
eventEmitted**Событие определённого типа, сгенерированное конкретным контрактом
logEmitted**Логи, сгенерированные конкретным контрактом, по совпадению префикса сырой записи лога
function**Прямой вызов заданной функции. Не применяется к внутренним вызовам между контрактами
ethBalance**Аккаунт или контракт, чей ETH-баланс удовлетворяет числовому условию в момент транзакции
stateChanged**Контракт, у которого переменная состояния on-chain изменилась во время транзакции, с опциональными условиями по значению/проценту
valueЗначение, передаваемое в транзакции, в wei
gasUsedКоличество газа, использованного транзакцией, в wei
gasLimitЛимит газа, установленный для транзакции, в wei
feeКомиссия транзакции в wei
contractСмарт-контракт, участвующий в транзакции (см. фильтр contract)
Триггер должен содержать network и хотя бы один из следующих фильтров: from, to, function, eventEmitted, logEmitted, ethBalance или stateChanged
Пример транзакции в Ethereum mainnet или Base, отправленной на 0x236..fd62.
transaction-trigger.yaml
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
В этом случае нас интересуют только транзакции, которые заминированы, и соответствующие одному из этих двух фильтров:
  • Фильтр 1: network 1 (Ethereum) AND status fail AND to 0x2364...fd62
  • Фильтр 2: network 8453 (Base) AND status fail AND to 0x2364...fd62
Поскольку мы не фильтровали по конкретному отправителю, все транзакции, приходящие на указанного получателя, приведут к срабатыванию Web3 Action.

Фильтрация транзакций по полям транзакции

Среди общих полей транзакции вы можете фильтровать по следующим свойствам с конкретными значениями.
  • from — фильтрация по конкретному отправителю. Может быть одиночный адрес или список адресов (OR). Опционально.
  • to — фильтрация по конкретному получателю. Может быть одиночный адрес или список адресов (OR). Опционально.
  • status — фильтрация по статусу транзакции: success или fail. Может быть одиночное значение или список (OR). Опционально.
  • network — фильтрация по сети. Может быть одиночный chain ID или список chain ID (OR). Обязательно.
  • contract.address — фильтрация только транзакций, участие в которых принимает контракт с этим конкретным адресом. Опционально.
Все поля адресов должны иметь префикс 0x и содержать 40 hex-символов (например, 0xf63c48626f874bf5604D3Ba9f4A85d5cE58f8019).
list-fields.yaml
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
Вы также можете запрашивать числовые значения, связанные с переводимой суммой и газом, используя операторы сравнения. Все следующие поля указываются в wei:
  • value
  • gasUsed
  • gasLimit
  • fee
Числовые поля принимают либо единственный объект сравнения, либо список объектов сравнения. Когда предоставлен список, сравнения объединяются логикой OR. Внутри одного объекта сравнения все операторы объединяются логикой AND. Вы можете использовать любой из общих операторов сравнения: eq, gte, gt, lt, lte. Также можно использовать булев флаг not для инвертирования сравнения.
ОператорЗначение
eqРавно
gtБольше чем
gteБольше или равно
ltМеньше чем
lteМеньше или равно
notБулев. Установите в true, чтобы инвертировать сравнение (например, совпадать, когда значение не в диапазоне)
Ниже приведён пример фильтра, демонстрирующий фильтры с использованием скалярных значений в полезной нагрузке транзакции.
numeric-filters.yaml
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 для инвертирования сравнения:
negated-comparison.yaml
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 для деталей.
  • name: имя события (требует верифицированного контракта).
  • id: topic-хеш сигнатуры события (альтернатива name). Полезно для неверифицированных контрактов, где ABI недоступен. Например, событие Transfer(address,address,uint256) имеет topic-хеш 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
  • not: булев. Установите в true для инвертирования всего совпадения события, включая параметры (срабатывает, когда событие не сгенерировано с указанными параметрами).
  • parameters: список условий на декодированные значения аргументов события. Все условия объединены AND: каждое условие должно совпадать. Каждая запись поддерживает:
    • name (обязательный): имя аргумента события.
    • string: сравнение строк. Принимает обычную строку для точного совпадения либо объект с полями exact и not. См. StringComparison для деталей.
    • int: целочисленное сравнение. Поддерживает те же операторы, что и числовые фильтры (eq, gt, gte, lt, lte). Все операторы в одном блоке int объединены AND. Используйте флаг not для инвертирования комбинированного условия. См. IntComparison для деталей.
В пределах одной записи eventEmitted в parameters нет OR. Все условия объединены AND. Для OR-логики на одном параметре используйте несколько записей eventEmitted (которые объединены OR во внешнем списке).
Чтобы использовать фильтр eventEmitted с name или parameters, контракт должен быть верифицирован и добавлен в проект в Tenderly Dashboard. Используйте id (topic-хеш) для неверифицированных контрактов.
Полное описание полей см. в EventFilter. Ниже приведён пример Web3 Action, который будет вызван при генерации события TxSubmission или TxConfirmation при выполнении смарт-контракта по адресу 0x418d..9d45 в Sepolia.
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:
event-parameters.yaml
filters:
  - network: 8453
    eventEmitted:
      contract:
        address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
      name: Transfer
      parameters:
        - name: from
          string: "0x8F362C3dd5301Ce1b43ea3A278E3f12c1807C271"
        - name: value
          int:
            gte: 1000000           # >= 1 USDC (6 decimals)
Числовой диапазон с инвертированием параметров:
negated-parameter.yaml
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:
event-parameter-or.yaml
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 на самом событии для инвертирования всего совпадения:
negated-event.yaml
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 может быть одиночным объектом или списком (записи объединены OR). Он поддерживает следующие свойства:
  • startsWith (обязательный): список значений topic для сопоставления с topic’ами логов транзакции. Каждая запись сравнивается по точному равенству (без учёта регистра) с соответствующим topic. На практике это полные 32-байтные topic-хеши (с префиксом 0x, 64 hex-символа), но поле принимает любую валидную hex-строку.
  • contract.address (опционально): источник логов. Когда указан, сопоставляются только логи из этого контракта.
  • matchAny: булев. При true достаточно совпадения любого одного topic из startsWith. При false (по умолчанию) должны совпасть все topic.
  • not: булев. Установите в true для инвертирования всего совпадения лога (срабатывает, когда лог не сгенерирован).
Использование logEmitted позволяет использовать префикс topic события в сыром виде для фильтрации событий. Это полезно, если вы настраиваете Web3 Action на неверифицированном контракте.
Полное описание полей см. в LogFilter. Ниже приведён пример Web3 Action, который будет вызван, когда транзакция содержит записи логов, начинающиеся с заданных префиксов.
log-emitted-filter.yaml
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. Фильтр contract поддерживает следующие свойства:
  • address: адрес контракта.
  • invocation: контролирует, как контракт был вызван. Возможные значения:
    • direct: контракт был вызван напрямую отправителем транзакции (EOA). Используйте, когда вас интересуют только вызовы верхнего уровня к контракту.
    • internal: контракт был вызван внутренне другим контрактом во время выполнения (например, через опкоды CALL, DELEGATECALL или STATICCALL). Используйте, чтобы обнаружить, когда ваш контракт вызывается как часть многоконтрактного взаимодействия.
    • any: совпадает и для прямых, и для внутренних вызовов. Значение по умолчанию.
Поле invocation доступно везде, где используется объект contract, включая фильтры eventEmitted. Например, вы можете использовать invocation: internal для контракта в eventEmitted, чтобы совпадать только с событиями, генерируемыми во время внутренних вызовов. См. Contract для схемы.
invocation не поддерживается в logEmitted. Для ограничения сопоставления логов используется только contract.address; тип invocation игнорируется.
В примере ниже первый фильтр совпадает с любой успешной транзакцией, отправленной на 0x2364...fd62 в Sepolia. Второй фильтр более ограничивающий: только транзакции, которые также включают внутренний вызов контракта 0xad88...80d6, запустят Web3 Action.
contract-filter.yaml
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. Он требует адрес контракта contract.address и либо name, либо signature функции. Фильтр function поддерживает следующие свойства:
  • contract (обязательный): для указания целевого контракта.
    • contract.address: адрес вызываемого контракта.
    • contract.invocation: контролирует, как функция должна быть вызвана: any (по умолчанию, и прямые, и внутренние), direct (только инициированные EOA) или internal (только вложенные вызовы). См. фильтр contract для деталей.
  • name: имя функции (требует верифицированного контракта).
  • signature: 4-байтовый селектор функции (например, 0xa9059cbb). Полезно для неверифицированных контрактов, где ABI недоступен.
  • not: булев. Установите в true для инвертирования всего совпадения функции, включая параметры (срабатывает, когда функция не вызвана с указанными параметрами).
  • parameters: список условий на декодированные значения входных аргументов функции. Все условия объединены AND: каждое условие должно совпадать. Каждая запись поддерживает:
    • name (обязательный): имя аргумента функции.
    • string: сравнение строк. Принимает обычную строку для точного совпадения либо объект с полями exact и not. См. StringComparison для деталей.
    • int: целочисленное сравнение. Поддерживает те же операторы, что и числовые фильтры (eq, gt, gte, lt, lte). Все операторы в одном блоке int объединены AND. Используйте флаг not для инвертирования комбинированного условия. См. IntComparison для деталей.
В пределах одной записи function в parameters нет OR. Все условия объединены AND. Для OR-логики по одному и тому же параметру используйте несколько записей function (которые объединены OR во внешнем списке).
Чтобы использовать фильтр function с name или parameters, контракт должен быть верифицирован и добавлен в проект в Tenderly Dashboard. Используйте signature (4-байтовый селектор) для неверифицированных контрактов.
Вы можете указать function как одиночный объект или как список. Когда предоставлен список, записи объединяются OR. Фильтр совпадает, если вызвана любая функция из списка. Полное описание полей см. в FunctionFilter. Ниже приведён пример триггера Web3 Action, реагирующего на любой вызов (прямой или внутренний) функции verySpecialFunction контракта, развёрнутого по адресу 0xad88...80d6.
function-filter.yaml
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:
function-selector.yaml
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:
function-parameters.yaml
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, вызванным маршрутизатором или провайдером flash-loan, а не напрямую пользователем, установите invocation: internal. Этот пример срабатывает только когда другой контракт внутренне вызывает transfer для USDC на сумму не менее 1000 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 запускает ваш Web3 Action, когда ETH-баланс аккаунта или контракта удовлетворяет числовому условию во время транзакции. Это полезно для отслеживания крупных перемещений «китов», пороговых значений казны протокола или событий пополнения кошелька. Фильтр поддерживает следующие свойства:
  • address (обязательный): адрес аккаунта или контракта для отслеживания.
  • balanceCmp (обязательный): числовое сравнение с балансом на момент транзакции. Использует операторы BigIntComparison (eq, gt, gte, lt, lte). Значения в wei и могут быть предоставлены как десятичные строки (например, "1000000000000000000") или как hex с префиксом 0x (например, "0xde0b6b3a7640000").
  • not (опционально): установите в true для инвертирования всего совпадения.
Можно предоставить одиночный объект ethBalance или список — записи объединены OR. Полное описание полей см. в EthBalanceFilter. Пример: срабатывает, когда баланс кошелька достигает не менее 1 ETH:
eth-balance-filter.yaml
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
Пример: несколько аккаунтов отслеживаются в одном фильтре (OR):
eth-balance-multi.yaml
filters:
  - network: 1
    ethBalance:
      - address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"   # vitalik.eth
        balanceCmp:
          gte: "1000000000000000000"
      - address: "0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8"   # Binance cold wallet
        balanceCmp:
          lte: "500000000000000000"   # below 0.5 ETH
Пример: баланс в диапазоне:
eth-balance-range.yaml
filters:
  - network: 1
    ethBalance:
      address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"   # vitalik.eth
      balanceCmp:
        gte: "1000000000000000000"   # >= 1 ETH
        lte: "2000000000000000000"   # <= 2 ETH
Пример: срабатывает, когда баланс кошелька не ниже 1 ETH (то есть инвертирование условия):
eth-balance-not.yaml
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 запускает ваш Web3 Action, когда переменная состояния контракта меняется во время транзакции. Вы можете сопоставлять по любой комбинации: изменилась ли переменная вообще, конкретное новое значение, процентное изменение или сырой ключ слота хранилища. Фильтр поддерживает следующие свойства:
  • address (обязательный, если не matchAny: true): адрес контракта для отслеживания.
  • matchAny (опционально): при true совпадает с изменениями состояния любого контракта в транзакции, address можно опустить. Полезно для мониторинга на уровне протокола.
  • params (опционально): список условий на переменные состояния. Все записи объединены AND. Когда опущен, фильтр совпадает с любым изменением состояния указанного адреса.
  • 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:
state-changed-basic.yaml
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 достигает порогового значения:
state-changed-value.yaml
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 изменилось И новое значение выше порога):
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
Пример: отслеживание конкретного слота отображения (mapping), например balances[bob], с использованием сырого ключа слота хранилища: Для отображений нет именованной переменной на запись — используйте storageSlotKey со слотом, полученным через keccak256, для интересующего ключа. Слот для mapping(address => uint256) balances в слоте хранилища N и ключе addrkeccak256(abi.encode(addr, N)).
state-changed-slot.yaml
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 могут комбинироваться с любым другим фильтром в одном и том же объекте фильтра — все поля объединены AND. Приведённые ниже примеры показывают распространённые реальные комбинации. Пример: mint() был вызван И totalSupply изменилось (подтверждение того, что вызов имел эффект в блокчейне):
state-changed-with-function.yaml
filters:
  - network: 8453
    function:
      name: mint
      contract:
        address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
    stateChanged:
      address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
      params:
        - name: totalSupply
          change: true
Пример: сгенерировано событие Transfer И изменилось состояние paused (переключён флаг владения или паузы во время перевода):
state-changed-with-event.yaml
filters:
  - network: 8453
    eventEmitted:
      contract:
        address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
      name: Transfer
    stateChanged:
      address: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
      params:
        - name: paused
          change: true
Пример: любой вызов контракта И ETH-баланс вызывающего выше порогового значения:
eth-balance-with-to.yaml
filters:
  - network: 8453
    to: "0x0232b6c44752020C645b65CAA68c139ab456Ec37"
    ethBalance:
      address: "0x4786f86f7d9d1467928b3518f123ea4fb65f1500"
      balanceCmp:
        gte: "500000000000000"   # caller has >= 0.0005 ETH
Пример: срабатывает, когда togglePause() был вызван, но balances не изменились (то есть транзакция затронула только флаг паузы):
state-changed-not.yaml
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. Используйте его как быструю справку при построении вашей конфигурации триггера. Определения типов см. в разделе Справочник схемы YAML-триггеров ниже.
complete-reference.yaml
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%

Справочник схемы YAML-триггеров

Приведённая ниже схема описывает структуру и типы каждого поля, принимаемого конфигурацией триггера. Используйте её вместе с аннотированным примером выше при построении вашего tenderly.yaml. Нотация: ? = опционально, | = одно из, [] = список. Поля, помеченные как «одиночный или список», принимают либо скалярное значение, либо YAML-список.

Все типы триггеров

schema.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

schema.yaml
# 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
В каждом фильтре должен присутствовать хотя бы один из: from, to, function, eventEmitted, logEmitted, ethBalance или stateChanged.
Поле contract верхнего уровня действует как общее значение по умолчанию для фильтра. Оно автоматически применяется к любой записи function или eventEmitted, которая не указывает свой собственный контракт, что позволяет не повторять один и тот же адрес. Оно не применяется к logEmitted.

Общие типы

schema.yaml
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

schema.yaml
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)

schema.yaml
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)

schema.yaml
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)

schema.yaml
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)

schema.yaml
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)