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

What is blockwatcher?

Smart contracts emit events and receive calls constantly: a transfer, an approval, a call to an administrative function, a change of ownership. A team that cares about a handful of these conditions is usually left with two bad options: poll a block explorer’s API on a timer and hope its rate limits and uptime cooperate, or write and operate a bespoke script against an RPC endpoint for every condition, one script per rule, each with its own crash handling and no shared guarantees. Neither approach scales past a few rules, and both leave a team owning infrastructure that has nothing to do with the business rule they actually care about.

blockwatcher is a long-running service built to take that watching-and-deciding work off a team’s hands, for any number of rules across any number of chains it has a module for.

What blockwatcher does

At the center of an blockwatcher deployment sit four kinds of resource, and configuring all four is what makes something happen. A network tells blockwatcher where to look: a chain and the RPC endpoint(s) to pull activity from. A contract spec tells it what a piece of on-chain data means (an ABI, compiled once into a schema the rest of the system reasons about from then on). A monitor ties a network to a spec, names which addresses and which events or functions to pay attention to via its selectors, and optionally narrows that further with a predicate, a condition over the decoded fields. An optional gate can require several such hits, or cap alerts, before anything is delivered. A sink says where a hit should go.

Wire all four together and the loop needs no further attention from you: blockwatcher keeps ingesting from the network and checking every new occurrence against every active monitor, dispatching whatever counts as a match; see How it works for that loop traced step by step. None of this needs a process restart to change: every one of the four resource kinds above is created, edited, and deleted through a REST API while blockwatcher keeps running, and an edit takes effect on the running pipeline within the same request.

What blockwatcher is not

blockwatcher doesn’t keep a queryable history. It has no store of past chain state you can run open-ended historical questions against: it reacts to activity as it arrives (or, for a one-off test run, over a bounded range you specify through the API), and what it retains about anything it has already processed is limited to its own operational bookkeeping: checkpoints, dead letters, counters.

It also isn’t a place to build dashboards or discover trends. There is no aggregation layer, no time-series rollup, no chart: a match either reaches a destination you control or ends up in blockwatcher’s dead-letter store; anything you do with the resulting stream happens downstream, in whatever receives it.

And it doesn’t participate in a chain. blockwatcher holds no key, has no consensus role, and issues no transactions of its own: it only reads, through whatever RPC endpoint a network names, and its own reliability is bounded by that endpoint’s.

The four promises

No silent gaps. A slow sink never causes blockwatcher to drop anything to keep up: the pipeline holds events in its internal queues under backpressure rather than discarding work, so a struggling delivery target slows the whole pipeline down instead of losing data out of it. Recovering from a crash follows the same rule: blockwatcher resumes exactly where its last persisted checkpoint says it left off, which can mean re-sending a match it had already delivered but never means skipping one it hadn’t reached yet. The one place this promise does not hold is the evm-mempool source: a pending transaction has no fixed chain position, only an arrival order, so a restart there can genuinely lose whatever was mid-flight. Full mechanics: Delivery guarantees.

Bad configuration never fires silently. The failure mode this closes is specific: a monitor that looks correctly wired, runs for months, and never once fires because one field name has a typo three levels deep in a predicate: nothing about a monitor like that announces its own mistake. blockwatcher forecloses it by parsing, type-checking, and compiling every predicate and selector against the schema its monitor actually exposes the moment it’s written, rejecting anything it can’t resolve and naming its best guess at the field you meant. Full mechanics: Predicates and the expression language.

No chain knowledge leaks into the core. Every chain family, however it encodes its own data, normalizes into the same canonical value model before anything past the decoder ever touches it (arbitrary-precision integers included, so a 256-bit token amount survives decode to delivery without losing precision). That boundary is enforced, not just assumed: the engine that runs pipelines, tracks checkpoints, and serves the API never imports a chain SDK, an HTTP client, or a storage driver, and a CI gate breaks the build the moment a core crate tries. Full mechanics: Chain-agnosticism.

Swap any stage without touching the rest. How blockwatcher reads a chain, decodes its data, evaluates conditions, gates hits, delivers matches, and persists state are six independent choices, each an interchangeable implementation behind a small port trait, selected in config by name. Only EVM chain modules ship with blockwatcher, but the boundary is what will let a new chain family, or a new delivery target, arrive as its own module without the engine changing to accommodate it. Full mechanics: Modules and trade-offs.