Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Monitor

A monitor is the rule that ties the other three kinds together: an id, the network it watches, one or more selectors, an optional predicate, an optional gate, and actions naming the sinks it delivers to (Monitor, crates/blockwatcher-types/src/resource.rs). It is written through PUT /monitors/{id} on the HTTP API or as one JSON file under a seed directory’s monitors/ subdirectory. Writing a monitor usually restarts nothing: the running pipeline hot-swaps the recompiled monitor set in place. Two things restart that one network instead: changing gate, whose previous envelope’s holds are dropped with the pipeline stopped and the pipeline then brought back (deleting a gated monitor takes the same path), and naming a sink the pipeline never spawned a worker for, per Resources § Lifecycle. Dropping holds never drops an already-emitted digest: an emission the old envelope committed but had not yet delivered survives the wipe and is delivered (or dead-lettered) when the pipeline comes back, see Gates § Delivery guarantee.

Monitor
  id         MonitorId
  network    NetworkId
  selectors  RawSelector[]
  predicate  string?          // this monitor's filter, not a shared id
  gate       ModuleSel?       // this monitor's decision rule, like predicate
  actions    SinkId[]

A complete monitor resource, selecting against the erc20 spec from the Spec page:

{
  "id": "usdc-transfers",
  "network": "eth-mainnet",
  "selectors": [
    {
      "spec": "erc20",
      "events": ["Transfer"],
      "addresses": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"]
    }
  ],
  "predicate": "args.value > 1000000",
  "actions": ["alerts"]
}

The same monitor with a threshold gate; omitting gate (the first example) remains valid passthrough:

{
  "id": "usdc-burst",
  "network": "eth-mainnet",
  "selectors": [
    {
      "spec": "erc20",
      "events": ["Transfer"],
      "addresses": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"]
    }
  ],
  "predicate": "args.value > 1000000",
  "gate": {
    "module": "threshold",
    "config": { "count": 3, "window_ms": 3600000 }
  },
  "actions": ["alerts"]
}

Fields

Monitor rejects an unrecognized top-level field (#[serde(deny_unknown_fields)]); a selector entry’s keys are the one exception, checked later by the chain’s decoder rather than by serde, per the selector table below.

KeyTypeDefaultMeaning
idstringnone (required)The monitor’s name; every delivered match carries it.
networkstringnone (required)The network this monitor watches. The write refuses an id that does not exist. Fixed at creation: an update naming a different network is refused (422); delete and recreate the monitor to move it.
selectorsarray of objectsnone (required)What to decode and match, OR’d across entries. An empty array refuses: a monitor needs at least one selector.
predicatestringabsentAn expression over the decoded event; absent matches everything the selectors decode.
gateobjectabsent{ "module", "config" }. Absent = passthrough. See Gates.
actionsarray of stringsnone (required)The sinks to deliver each match to, one or more. The write refuses an id that does not name an existing sink.

The whole monitor (selectors, predicate, and gate) must compile against the live decoder, matcher, and gate catalog before the write is persisted, so every refusal quoted below surfaces as a 422 on the write, per Resources § Write-time validation.

Selector keys

Each selector entry carries a core-readable spec reference plus decoder-owned keys. For the evm decoder those are events, functions, and addresses, all optional; any other key refuses by name at compile time: unknown selector key '<key>' (compile, crates/blockwatcher-evm/src/decoder/selector.rs).

KeyTypeDefaultMeaning
specstringnone (required)The spec this entry decodes against. A missing key refuses: selector requires a 'spec' reference. The spec must exist and share the network’s chain.
eventsarray of stringsabsentEvent names to select from the spec, all overloads of each name. Absence and emptiness mean opposite things, per the rule below.
functionsarray of stringsabsentFunction names to select from the spec, same shape as events.
addressesarray of stringsabsentContract addresses this entry is restricted to; absent means any address. Each entry restricts only the events and functions it names, never a sibling entry’s.

The absent-vs-empty rule

events and functions share one presence matrix:

  • Naming neither selects every event AND every function the spec declares, ABI-scoped rather than chain-wide, mirroring how an absent addresses means any address.
  • Naming either selects only what it names; the omitted kind is left unselected.
  • An explicit empty array is refused rather than treated as absence, because the two spellings mean opposite things and the empty one would compile an entry that can never fire: selector's 'events' is empty: omit the key to select every declaration of that kind in the spec, or name at least one (functions and addresses refuse with the same shape; the addresses message ends omit the key to match any address, or name at least one).

What else a selector refuses

  • A non-string entry is refused by position, never silently dropped: selector's 'events' entry at position 1 is not a string; every entry must be a name.
  • A name the spec does not declare is refused with a suggestion, the spec’s first declared event or function of that kind: unknown field 'Transfr' plus did you mean 'Approval'? (see Resources § The did-you-mean suggestion).
  • An address must be a 0x-prefixed 40-hex-digit string: address '<raw>' must be a 0x-prefixed 40-hex-digit string.
  • A mixed-case address claims an EIP-55 checksum and is held to it: address '<raw>' is mixed-case, which claims an EIP-55 checksum, but the checksum does not match; check for a mistyped character. An all-lowercase or all-uppercase spelling makes no checksum claim and is accepted as-is.

What a selected event or function actually decodes to, and which raw material reaches the selector per source, is documented on Selectors.

Predicate

predicate is one expression string, compiled at write time against the schemas the monitor’s selectors produce; a field or namespace it names that no selected schema declares refuses the write, with a bounded edit-distance suggestion. The language (syntax, operators, the three-valued evaluation, and the type families) is documented on Predicates and the expression language.

Gate

gate compiles at write against the same schemas as the predicate. Refusals (all 422):

  • unknown field on the envelope or inside config (deny_unknown_fields)
  • unknown module (message lists catalog gate names)
  • window_ms 0 or > 86400000: same bound language as sink throttle.window_ms
  • threshold.count < 2 or > 10000
  • missing block.timestamp on the schema: gate requires 'block.timestamp'; this monitor's selectors do not expose it

Changing module or config drops persisted holds for that monitor; they are not migrated.