# 🦀 rflow 🦀 > rflow is a blazing-fast onchain workflow engine written in Rust — one rflow.yaml maps triggers to chains of actions with exactly-once execution ## Docs - [Approvals — human-in-the-loop sends](/approvals): Some transactions should not fire without a human saying so — big treasury moves, emergency responses, anything above a threshold. `approval:` on a [`send_transaction`](/workflows/steps/send-transaction) step parks the run with the transaction **fully prepared and simulated**, notifies the approvers, and only broadcasts after an explicit yes. - [Backtesting — replay, test & dry-run](/backtesting): Would your workflow have done the right thing? `rflow replay` answers with **real historical blocks**: it re-indexes a range, fires your trigger exactly as live would, runs every step in dry-run mode — simulation, gas caps, policy checks, `assert_sim`, `recheck` all included — and reports what it *would have sent*. `rflow test` is the single-shot sibling for one fixture or one real transaction — for **every** trigger kind. Nothing broadcasts unless you explicitly go `--live`. - [Benchmarks](/benchmarks): rflow's performance claim is architectural: event decode → expression eval → relayer hand-off are **in-process function calls** — no HTTP hop, no serialization on the hot path. This page puts numbers on that, and is precise about what was measured and what was not. - [CLI](/cli): The `rflow` binary is a pure interface over the core engine. Every command accepts `--path ` (short `-p`) to point at a project directory; the default is the current directory. - [MCP server](/mcp): `rflow mcp` serves the [Model Context Protocol](https://modelcontextprotocol.io) over stdio, so AI agents and editors — Claude Code, Cursor, or anything else that speaks MCP — can **operate** an rflow project: validate config, inspect the run journal and indexer cursors, simulate contract calls, and perform the same journaled, safe mutations the CLI exposes. No other workflow engine ships this: your agent debugs a dead-lettered run, simulates the fix, and retries it without ever touching a key. - [Observability](/observability): rflow's single exposed port (`config.port`, default `3940`) carries everything you need to watch a deployment: the health probe, a Prometheus `/metrics` endpoint, a built-in run-trace viewer and the read-only JSON API behind it. - [Reliability & exactly-once](/reliability): "Exactly-once" is an easy claim and a hard contract. This page explains exactly what rflow guarantees, how, and what it deliberately does not promise. - [Reorgs & confirmations](/reorgs): Chains reorganize. rflow's stance: **you choose the trade-off per trigger, rflow advises, and nothing is hidden.** - [Self-hosting](/self-hosting): rflow is one binary plus one Postgres. There is nothing else to run. - [Budgets — durable spend caps](/workflows/budgets): `permissions.max_value_per_tx` caps **one** send. A **budget** caps **cumulative** spend inside a rolling window across *every* workflow, run and send that attaches it — the blast-radius cap when something upstream misbehaves (a bad price, a runaway loop, a compromised trigger). Budgets are durable and reservation-based: racing runs cannot overshoot a cap, and a restart cannot reset one. - [Expressions](/workflows/expressions): One `${{ ... }}` language everywhere: conditions (`where` / `if` / `assert` / `retry_if` / `condition` / `recheck`) and values (args, messages, urls, bodies) all render through the same engine. It is GitHub-Actions-shaped on the outside and [minijinja](https://github.com/mitsuhiko/minijinja)-powered underneath, tuned for EVM work: strict U256 math, case-insensitive addresses, and strict-undefined behavior (a typo'd key is an error, never a silent `false`). - [Workflows](/workflows): A workflow is *when this happens, do these things, in order*. Each entry under `workflows:` (keyed by name) has one trigger and a list of steps. - [Block trigger](/workflows/triggers/block): Fire a workflow every N blocks on a network — keeper duties, per-block checks, periodic onchain maintenance that should track chain time rather than wall time. - [Composite triggers — ](/workflows/triggers/composite): A composite trigger fires a workflow from **several** on-chain events instead of one. - [Cron trigger](/workflows/triggers/cron): Fire a workflow on a schedule. No chain config needed — a cron → HTTP → notify project runs with zero networks and zero signers. - [Event trigger](/workflows/triggers/event): Fire a workflow on a decoded onchain event. - [Interval trigger](/workflows/triggers/interval): Fire a workflow on a fixed wall-clock cadence — `every: 30s | 1m | 5m`. No chain config needed. Unlike [cron](/workflows/triggers/cron), intervals allow **sub-minute** cadences, so a 30-second keeper needs no cron hacks. - [Query trigger](/workflows/triggers/query): Poll a read-only SQL **aggregate over the indexed event tables** on an interval and fire when a condition is met — "page me when 24h volume crosses X", "act when cumulative deposits pass a threshold" — without an external cron + API. - [Read trigger](/workflows/triggers/read): Poll a view function on an interval and fire when a condition is met — health factors, utilization, oracle staleness, anything an `eth_call` can see. - [Stream trigger (WebSocket)](/workflows/triggers/stream): Start a workflow from messages on an external WebSocket feed — exchange prices, order books, liquidation feeds, oracle vendor feeds, bridge status, internal event streams. Each JSON message is filtered by an expression and, if it matches, becomes an ordinary rflow run. - [Web trigger (HTTP poll)](/workflows/triggers/web): Start a workflow from a web data source — protocol announcements, governance forum posts, security advisories, oracle incident pages, exchange listings, risk feeds, status pages, internal APIs. rflow polls an HTTP(S) URL on an interval, parses the body into **items** (RSS/Atom entries, JSON array elements, or the text of HTML nodes), and fires one run per **new** item — or one run per **content change** with `changed: true`. - [Webhook trigger](/workflows/triggers/webhook): Fire a workflow on an inbound HTTP POST. The route lives below rflow's single port (`config.port`) — no extra listener, no extra exposure. - [command](/workflows/steps/command): Run a project-owned command that **decides, transforms, enriches, scores, or prepares** data. rflow renders an `input:` object, pipes it to the command as JSON on stdin, and stores what the command prints on stdout as `steps..output` for later steps to consume. - [delay](/workflows/steps/delay): Pause the run for a duration — **durably**. - [http_call](/workflows/steps/http-call): Outbound HTTP with templated url/body and optional HMAC signing. - [notify](/workflows/steps/notify): Send a templated message to a configured [notification channel](/config/notifications). - [query](/workflows/steps/query): Read-only SQL over the project's **indexed event tables** (and the rflow journal) — decisions over history, not just the triggering event. With `assert:` it becomes a hard gate; without, it's enrichment for later steps. - [read](/workflows/steps/read): `eth_call` a view function. With `assert:` it becomes a hard gate; without, it's enrichment for later steps. - [send_transaction](/workflows/steps/send-transaction): Queue a transaction through the embedded relayer. - [state_set, list_add & list_remove](/workflows/steps/state-and-lists): Three small steps that give workflows durable memory: a Postgres-backed key/value store (`state_set`) and runtime mutation of your [`lists:`](/config/constants-secrets-lists#lists) watchlists (`list_add` / `list_remove`). Plus the `previous_run` context root for comparing against the last run. - [wait_for](/workflows/steps/wait-for): Park the run — durably — until the **first** of several conditions. This is the cross-chain saga construct: send on chain A, wait for the matching event on chain B, continue (or compensate on timeout). - [Aave v3 health guardian](/use-cases/aave-health-guardian): **Template:** `aave-health-guardian` · **category:** keeper · **risk:** `money_moving` - [Bridge message watch](/use-cases/bridge-message-watch): **Template:** `bridge-message-watch` · **category:** bridge-ops · **risk:** `monitor_only` - [Chainlink deviation alert](/use-cases/chainlink-deviation-alert): **Template:** `chainlink-deviation-alert` · **category:** monitoring · **risk:** `monitor_only` - [Chainlink oracle staleness](/use-cases/chainlink-oracle-staleness): **Template:** `chainlink-oracle-staleness` · **category:** monitoring · **risk:** `monitor_only` - [Command decision](/use-cases/command-decision): **Template:** `command-decision` · **category:** examples · **risk:** `monitor_only` - [Command trade prep](/use-cases/command-trade-prep): **Template:** `command-trade-prep` · **category:** relayers · **risk:** `money_moving` - [Cron HTTP report](/use-cases/cron-http-report): **Template:** `cron-http-report` · **category:** offchain · **risk:** `monitor_only` - [Cross-chain settlement saga](/use-cases/cross-chain-settlement-saga): **Recipe** (no bundled template) · **category:** relayers · **risk:** `money_moving` - [ERC-20 supply watch](/use-cases/erc20-supply-watch): **Template:** `community/erc20-supply-watch` · **category:** monitoring · **risk:** `monitor_only` · **community** - [ERC-4626 vault monitor](/use-cases/erc4626-vault-monitor): **Template:** `erc4626-vault-monitor` · **category:** monitoring · **risk:** `monitor_only` - [ERC-721 floor sweep watch](/use-cases/erc721-floor-sweep-watch): **Template:** `erc721-floor-sweep-watch` · **category:** monitoring · **risk:** `monitor_only` - [ERC-7683 intent solver](/use-cases/erc7683-solver): **Template:** `erc7683-solver` · **category:** intents · **risk:** `money_moving` - [Hot wallet refill](/use-cases/hot-wallet-refill): **Recipe** (no bundled template) · **category:** treasury · **risk:** `money_moving` - [Cookbook](/use-cases): Copyable recipes for real onchain jobs. Each page says **what it does**, **when to use it**, the **YAML**, the **env vars/secrets** it needs, a **risk label**, and **safety notes** — and links to a runnable template or example where one exists. - [Large approval alert](/use-cases/large-approval-alert): **Template:** `community/large-approval-alert` · **category:** security · **risk:** `monitor_only` · **community** - [Large transfer alert](/use-cases/large-transfer-alert): **Template:** `large-transfer-alert` · **category:** monitoring · **risk:** `monitor_only` - [Liquidation keeper](/use-cases/liquidation-keeper): **Template:** `liquidation-keeper` · **category:** keeper · **risk:** `money_moving` - [Native spend report](/use-cases/native-spend-report): **Template:** `native-spend-report` · **category:** treasury · **risk:** `monitor_only` - [Price alert stream](/use-cases/price-alert-stream): **Template:** `price-alert-stream` · **category:** monitoring · **risk:** `monitor_only` - [Proxy upgrade alert](/use-cases/proxy-upgrade-alert): **Template:** `proxy-upgrade-alert` · **category:** security · **risk:** `monitor_only` - [Relayer low balance alert](/use-cases/relayer-low-balance-alert): **Template:** `relayer-low-balance-alert` · **category:** relayers · **risk:** `monitor_only` - [Safe multisig monitor](/use-cases/safe-monitor): **Template:** `safe-monitor` · **category:** security · **risk:** `monitor_only` - [Safe owner change alert](/use-cases/safe-owner-change-alert): **Template:** `safe-owner-change-alert` · **category:** security · **risk:** `monitor_only` - [Security advisory watch](/use-cases/security-advisory-watch): **Template:** `security-advisory-watch` · **category:** security · **risk:** `monitor_only` - [Solver inventory rebalance](/use-cases/solver-inventory-rebalance): **Template:** `solver-inventory-rebalance` · **category:** intents · **risk:** `money_moving` - [Solver PnL report](/use-cases/solver-pnl-report): **Template:** `solver-pnl-report` · **category:** intents · **risk:** `monitor_only` - [Stablecoin pause / blacklist monitor](/use-cases/stablecoin-pause-monitor): **Recipe** (no bundled template) · **category:** monitoring · **risk:** `monitor_only` - [Timelock monitor](/use-cases/timelock-monitor): **Template:** `timelock-monitor` · **category:** governance · **risk:** `monitor_only` - [Token deposit relay](/use-cases/token-deposit-relay): **Template:** `token-deposit-relay` · **category:** relayers · **risk:** `money_moving` - [Treasury sweep with approval](/use-cases/treasury-sweep-approval): **Template:** `treasury-sweep-approval` · **category:** treasury · **risk:** `money_moving` - [Uniswap v2 price alert](/use-cases/uniswap-v2-price-alert): **Template:** `community/uniswap-v2-price-alert` · **category:** monitoring · **risk:** `monitor_only` · **community** - [Uniswap v3 LP monitor](/use-cases/uniswap-v3-lp-monitor): **Template:** `uniswap-v3-lp-monitor` · **category:** monitoring · **risk:** `monitor_only` - [Webhook idempotent handler](/use-cases/webhook-idempotent-handler): **Template:** `webhook-idempotent-handler` · **category:** offchain · **risk:** `prepares_tx` - [WETH wrap monitor](/use-cases/weth-wrap-monitor): **Template:** `community/weth-wrap-monitor` · **category:** monitoring · **risk:** `monitor_only` · **community** - [Workflow error pager](/use-cases/workflow-error-pager): **Template:** `workflow-error-pager` · **category:** monitoring · **risk:** `monitor_only` - [Community registry](/templates/community-registry): The template registry has two tiers in one repo, behind one trust gate (PR review into master): - [Creating templates](/templates/creating-templates): Templates are **data, not code**: a package is a manifest plus files. There are no generation hooks and no template-supplied code execution — rendering is plain substitution, and every install path ends in `rflow validate`. Adding a template to the registry is adding a directory; no code or index edit is required (the registry discovers packages at build time). - [Templates](/templates): rflow ships a first-party **template registry**: 27 productized recipes that turn "I want to watch a Safe / relay deposits / page on dead-letters" into a validated, production-shaped project in one command. A template is not a snippet — it knows rflow semantics end to end: typed inputs, networks, contracts, packaged ABIs, relayers, notification channels, secrets, safety defaults, replay fixtures, a docs recipe and snapshot tests. - [Using templates](/templates/using-templates): Three commands cover the whole lifecycle: **browse** the registry, **scaffold** a new project, **compose** a template into an existing one. - [Doctor & explain — pre-deploy diagnosis and PR review](/operations/doctor-and-explain): Two commands cover the "is this safe to boot?" question from both ends: - [History explorer — what ran, why, what it touched](/operations/history-explorer): rflow already journals **every** run and step (see [reliability](/reliability)). The history explorer turns that durable journal into an operational surface that answers the questions logs can't: *which workflows ran in the last hour, which runs sent transactions and what was the final status, what is parked right now, who approved a send, what changed between a good run and a bad one, and can I export a redacted audit trail?* - [Safety lint & the CI gate](/operations/lint-and-ci): `rflow validate` answers *"is this config correct?"* — `rflow lint` answers *"is it safe to run in production?"*. A workflow can be perfectly valid YAML and still broadcast unsimulated sends with no gas ceiling off an unauthenticated webhook. Lint holds every workflow against a **policy**: the recommended defaults out of the box, tunable via an optional top-level `policy:` block. `rflow ci` wraps lint, validate and every other unattended check into one command with one exit code. - [Retention, pruning & archive — bound the journal safely](/operations/retention-and-archive): rflow journals **every** run and step forever by default. That is the right default — history is how you debug incidents, prove what a keeper did, and reconstruct a run. But in long-running production the journal grows without bound, and eventually someone reaches for a manual `DELETE`. - [Spend tracking — what rflow actually paid](/operations/spend-tracking): Budgets cap what a send *may* spend; operators also need to know what rflow *did* spend. Spend tracking is a durable, receipt-derived ledger (`rflow.native_spend`) written at every send's terminal settle, answering: *how much native gas did rflow burn — by workflow, by relayer, by network — how much native value moved, and how much gas was lost to reverted transactions?* - [State inspection & repair — supported recovery, not manual SQL](/operations/state-inspection-and-repair): A durable workflow engine will eventually get something stuck: a bridge message that never arrives leaves a saga parked on a `wait_for:` forever; a bad RPC or config trips a circuit breaker; an approval nobody answers sits pending; an indexer falls behind and a cursor needs re-scanning. When that happens you need **supported, journal-aware repair commands** — not a `psql` prompt and a prayer. - [Unattended ops — alerting when nobody is watching](/operations/unattended-ops): Automation you don't watch needs to tell you two things on its own: **"a run broke"** and — the harder one — **"nothing is happening when something should be"**. rflow ships three journal-backed guarantees for that: - [Versioning & config plan — audit every deploy](/operations/versioning-and-plan): Every run should be traceable to the **exact** config that produced it, and every config change should be **reviewable before it goes live**. rflow builds this in: it fingerprints your `rflow.yaml` on every boot, stamps that version onto every run, and gives you a semantic `plan` that flags dangerous changes (and the parked work they collide with) before a restart. - [Migrating from OpenZeppelin Defender](/migrate/from-defender): OpenZeppelin Defender was sunset on July 1, 2026. If you ran Monitors, Relayers and Actions there, rflow covers the same ground — self-hosted, in one binary, with stronger execution guarantees. This page maps every Defender concept to its rflow equivalent and shows the one-command importer. - [Migrating from Gelato](/migrate/from-gelato): Gelato deprecated Web3 Functions on March 31, 2026, and the classic Automate task products are winding down with them. Time-based tasks, event listeners and recurring contract calls all have direct rflow equivalents — self-hosted, with exactly-once execution instead of best-effort. This page maps every Gelato concept and shows the one-command importer. - [What is rflow?](/introduction/what-is-rflow): rflow is an open-source, self-hosted onchain workflow engine. You describe automations in one `rflow.yaml` — *when this event fires on this chain, and this condition holds, send this transaction over there, then tell me about it* — and rflow runs them with exactly-once execution, pre-flight simulation, reorg awareness, and a durable journal you can inspect at any time. - [Why rflow?](/introduction/why-rflow): rflow is Artemis without writing Rust, Defender without the vendor, Temporal without the SDK — a single fast binary where GitHub-Actions-shaped YAML gets exactly-once, reorg-aware, simulation-gated automation. Self-hosted, any custody, any EVM chain — or no chain at all. - [Examples](/getting-started/examples): Runnable, self-contained example projects live in the repo's [`examples/`](https://github.com/joshstevens19/rflow/tree/master/examples) directory. Each one is a complete rflow project (`rflow.yaml` + `abis/` + `Makefile` + `README.md`) driven entirely by make — you type a few targets, watch the logs, and see real transactions relayed to CONFIRMED. Every example uses its own database, anvil port and health port, so they can all run side by side. - [Installation](/getting-started/installation): rflow uses Postgres for all state and scaffolds a `docker-compose.yml` for you, so it is recommended to install [docker](https://www.docker.com/products/docker-desktop/) if you don't have it already. - [Quickstart](/getting-started/quickstart): From nothing to a firing workflow in five commands. - [Scaffolding & config wizard](/getting-started/scaffolding): rflow is YAML-first — the whole project lives in a `rflow.yaml` you can read, diff, and review. The scaffold wizard makes the first-run details (RPCs, networks, contracts, relayers, signers, notifications) fast to get right, without ever taking the file out of your hands. - [ABIs — fetch, inspect & discover](/config/abis): Every [contract](/config/contracts) in `rflow.yaml` points at a **committed ABI json file** (`abi: ./abis/usdc.json`). This page covers the CLI helpers that get those files onto disk and answer questions about them — fetching the verified ABI for an address, adding a contract by address alone, inspecting events and selectors, and reverse-mapping topic hashes. - [config](/config/config): Engine-level settings. - [constants, secrets & lists](/config/constants-secrets-lists): Three ways to get values into your expressions. - [contracts](/config/contracts): A **global registry** — every contract declared here is usable from any workflow, trigger, or step by name. - [rflow.yaml](/config): One file defines the whole project. Everything below `rflow_version`, `name`, `config` and `workflows` is optional — no signer is needed to monitor, no networks are needed for cron + HTTP automations. - [networks](/config/networks): One `networks:` list feeds **both** embedded engines — indexer settings and relayer settings live side by side on the same entry. Optional: pure off-chain projects (cron → HTTP → notify) declare no networks and neither engine boots. - [notifications](/config/notifications): Named channels that [notify steps](/workflows/steps/notify) send into. rflow ships its own senders — no external webhook relays needed. - [profiles](/config/profiles): Per-environment overrides selected at runtime with `--profile ` — one `rflow.yaml`, many environments: - [relayers](/config/relayers): Named wallets your workflows send transactions from. The names are **your labels** — nothing about them is special, and you never touch a relayer API or manage the name↔key mapping yourself. - [external secret providers](/config/secret-providers): Fetch [`secrets:`](/config/constants-secrets-lists#secrets) values from **AWS Secrets Manager** or **GCP Secret Manager** at boot, so production API keys, webhook HMAC keys and notification tokens never live in `.env` or a mounted file. Optional: **`.env` stays the zero-config default** — a project with no `secret_providers:` behaves exactly as before, and both forms coexist in one `secrets:` map. - [signer](/config/signer): The signing provider — the key material your [relayers](/config/relayers) derive wallets from. Exact rrelayer `signing_providers` schema, all 9 providers. Optional: monitoring-only projects declare no signer and the relayer engine never boots.