Scaffolding & config wizard
The whole project lives in one rflow.yaml you can read, diff, and review.
Three commands scaffold and grow it:
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
(27 recipes, each with an id, risk label and 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 (default template:
large-transfer-alert). It writes the template's files (rflow.yaml, ABIs,
.env + .env.example, docker-compose.yml with postgres on
localhost:5448, .gitignore, .rflow/template-lock.yaml), then runs
rflow validate and prints the 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 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. 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 ...Each subcommand appends one entry to the matching top-level section
(networks:, contracts:, relayers:, notifications.channels:).
It preserves your file byte-for-byte
add splices the rendered snippet at the end of the matching section; it never
re-serializes the document, so comments, key ordering, spacing, and the trailing
newline all survive. The only change to the file is the added lines. An absent
section is created; a present-but-empty one is filled by the first entry.
add targets the 2-space indentation style rflow new scaffolds. A section
in another style (zero-indented list items, 4-space indents) refuses the add
with the file untouched. 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, and the
message says why. Errors already present in the file are surfaced but do not
block. Every successful add runs rflow validate and prints the next
command(s), including a rflow doctor hint.
These checks need the current rflow.yaml to parse, so a ${VAR} placeholder
in a strictly-numeric field like chain_id 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. For CI, agents, and non-TTY shells, pass flags; a missing required flag 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;
set the real value in .env. Passing only the kind flag uses these defaults:
| 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