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 (an EVM example; other families’ selector keys are on
the family 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
id | string | none (required) | The monitor’s name; every delivered match carries it. |
network | string | none (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. |
selectors | array of objects | none (required) | What to decode and match, OR’d across entries. An empty array refuses: a monitor needs at least one selector. |
predicate | string | absent | An expression over the decoded event; absent matches everything the selectors decode. |
gate | object | absent | { "module", "config" }. Absent = passthrough. See Gates. |
actions | array of strings | none (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. spec is required: a missing key refuses
selector requires a 'spec' reference. The spec must exist and share the
network’s chain. Every other key is admitted or refused by that chain’s
decoder, by name, at compile time. Which keys a family admits, the
absent-vs-empty rule for kind lists, and address spelling, are on the
family page.
What a selected kind actually decodes to, and which raw material reaches the selector per source, is also on the family page. Shared contracts (entries are OR’d, unknown keys fail closed) stay 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_ms0or> 86400000: same bound language as sinkthrottle.window_msthreshold.count< 2or> 10000- missing
block.timestampon 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. If the pipeline cannot be brought back afterwards, the
write is not 2xx and /status reports the network abandoned; persist
may already have landed.