Scaffolding & config wizard
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.
Three commands cover the whole lifecycle:
rflow new— create a project (interactive, or one-shot for CI).rflow add— grow an existingrflow.yaml, one section at a time.rflow doctor— check that Docker/Foundry/Postgres are ready (and, inside a project, run the deep operational suite).
rflow new / rflow init
rflow new [--output <dir>] [--name <project-name>] [--template <id>] [--yes]
[--answer key=value ...] [--answers-file answers.yaml]Interactive on a TTY: a picker lists the template registry —
17 production-shaped recipes, each with an id, a risk label and a summary —
then asks one typed prompt per template input (addresses are
checksum-validated, crons parsed, amounts checked; a bad answer is re-asked).
On a non-TTY, or with --yes, manifest defaults fill every unanswered input
(the default template is large-transfer-alert). It writes the template's
files (rflow.yaml, ABIs, .env + .env.example, docker-compose.yml with
postgres on localhost:5448, .gitignore and a .rflow/template-lock.yaml
install record), then runs rflow validate and prints the exact next steps.
--template <id> picks a template directly — rflow templates ls to browse,
rflow templates show <id> for its inputs, --answer key=value /
--answers-file to drive it non-interactively (CI/agents). Legacy scaffold
ids (transfer-alert, cron-report, treasury-approval,
liquidation-keeper) resolve to their registry equivalents; webhook-relay
and blank still use the pre-registry renderer.
A template that requires a signer (the money_moving ones) scaffolds a
DEV-ONLY generated mnemonic into .env — never into .env.example — so the
project validates and boots out of the box; swap in a real signer before
production. rflow new refuses to overwrite an existing rflow.yaml and
leaves an existing .env untouched, so it is safe to re-run in a directory
that already has secrets. See Using templates
for the full walkthrough, including rflow add workflow composition.
rflow add — grow an existing project
rflow add [--path <dir>] network ...
rflow add [--path <dir>] contract ...
rflow add [--path <dir>] relayer ...
rflow add [--path <dir>] notification ...rflow new gets you a project; rflow add grows it. Each subcommand appends one
entry to the matching top-level section (networks:, contracts:, relayers:,
notifications.channels:).
It preserves your file byte-for-byte
add locates the right section and splices the rendered snippet at the end of that
block. It does not parse the whole document to a value and re-serialize it —
that would strip your comments and reflow your formatting. On a money config that
matters. Comments, key ordering, spacing, and the trailing newline all survive; the
only change to the file is the lines that were added. If the section is absent, it
is created; if it is present-but-empty, the first entry fills it.
One expectation: add targets the conventional 2-space indentation style
rflow new scaffolds. If a section uses another style (zero-indented list items,
4-space indents), the add is refused with the file untouched and an error that
says so — re-indent the section or add the entry by hand.
It validates the merge and refuses to break the file
Before writing, add:
- rejects a duplicate name (a network/contract/relayer/channel that already exists),
- does a light shape check (contract address form, relayer network references), then
- validates the merged config in memory and refuses (exit 1, file untouched) any change that would introduce a hard validation error.
So rflow add relayer on a project with no signer: block is refused — a relayer
with nowhere to sign does not validate — and the message tells you why. Errors that
were already present in the file before your add are surfaced but do not block
(you asked to add a thing; a pre-existing problem elsewhere is not yours to fix
right now). Every successful add runs rflow validate and prints the next
command(s), including a rflow doctor hint.
The duplicate-name and reference checks need the current rflow.yaml to parse, so
a ${VAR} placeholder in a strictly-numeric field like chain_id (which never
validates) produces a "current config is not valid" message instead of an append —
fix the field first, then add.
Interactive, or flag-driven for CI and agents
On a TTY every subcommand prompts (a network-preset picker, a channel-kind menu, env-var prompts). For CI, agents, and non-TTY shells, pass flags. A non-TTY run with a required flag missing errors with exactly which flags are needed.
# a network (preset or custom); --rpc takes a url or a ${ENV} placeholder
rflow add network --name base --chain-id 8453 --rpc '${BASE_RPC}' --confirmations 12
# a contract, single-network or multi-network
rflow add contract --name USDC --abi ./abis/erc20.json --network base --address 0x8335...2913
rflow add contract --name USDC --abi ./abis/erc20.json --addresses ethereum=0xA0b8...eB48,base=0x8335...2913
# no ABI on disk yet? `rflow contract add` fetches the verified one first
# (sourcify -> etherscan) and then runs the same add - see /config/abis
rflow contract add USDC --network ethereum --address 0xA0b8...eB48
# a relayer (needs a signer already configured, else the add is refused)
rflow add relayer --name payout --networks ethereum,base --speed FAST
# a notification channel — one kind flag, plus the env-var names to read secrets from
rflow add notification --name ops --slack --webhook-url-env SLACK_WEBHOOK_URL
rflow add notification --name pager --telegram --bot-token-env TG_BOT_TOKEN --chat-id-env TG_CHAT_ID--path is global — it works before or after the subcommand.
Env-var flags take the variable name, not the secret. --webhook-url-env SLACK_WEBHOOK_URL renders webhook_url: ${SLACK_WEBHOOK_URL} into the YAML, and
you set the real value in .env. When you pass only the kind flag, sensible
defaults are used:
| Channel | Flags | Default env var(s) |
|---|---|---|
--telegram | --bot-token-env, --chat-id-env | TG_BOT_TOKEN, TG_CHAT_ID |
--slack | --webhook-url-env | SLACK_WEBHOOK_URL |
--discord | --webhook-url-env | DISCORD_WEBHOOK_URL |
--pagerduty | --routing-key-env | PAGERDUTY_ROUTING_KEY |
--opsgenie | --api-key-env [--eu] | OPSGENIE_API_KEY |
--twilio | --account-sid-env, --auth-token-env, --from, --to | TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN |
rflow doctor — local readiness
rflow doctor [--path <dir>] [--profile <name>] [--deep] [--json]A lean readiness table for the local dependencies rflow needs:
rflow doctor
dependency | status | detail
docker | ok | Docker version 28.5.2, build ecc6942
anvil | ok | anvil Version: 1.6.0-v1.7.0
cast | ok | cast Version: 1.6.0-v1.7.0
node | ok | v25.9.0
postgres | ok | reachable
rflow.yaml | ok | valid - 1 workflow(s), 0 warning(s)- Docker — daemon reachable (
docker info). - Foundry —
anvilandcastonPATH, with versions. - Postgres — reachable via the project's
config.db_connection(when a project is present). - Node — optional, for the docs site.
- rflow.yaml — the project's validate summary (workflows + warnings), when a project is found.
Each row is ok / missing / n/a, and any non-ok row prints a one-line "how to
install or start" hint.
When a project is present, the readiness table is followed by the deep
operational suite — profile merge, DB schema, RPC chain-id/capability probes,
contract sanity, relayer mappings and balances, channels, webhooks, approval
posture, and coverage notes. Exit code: non-zero only when an error-severity
check fails; warnings, advice and info never gate. The full check catalogue,
severities and the CI gating pattern live in
Doctor & explain. (For strict collect-all
config errors, use rflow validate.)
A full first run
rflow new --name payments --template treasury-sweep-approval --yes # scaffold a sending project
docker compose up -d # postgres on localhost:5448
rflow add network --name base --chain-id 8453 --rpc '${BASE_RPC}'
rflow add contract --name USDC --abi ./abis/erc20.json --network base --address 0x8335...2913
rflow doctor # readiness check
rflow validate # strict, collect-all
rflow start # boot the engines