Quickstart
From nothing to a firing workflow in five commands.
Install
curl -L https://rflow.sh/install.sh | bashScaffold a project
mkdir transfer-alert && cd transfer-alert
rflow newA picker lists the template registry's 27 recipes. Pick one
and answer its prompts, or run rflow new --yes to take the default with no
prompts (large-transfer-alert, the one this page follows).
It writes a complete project. The files that matter:
transfer-alert/
├── rflow.yaml # the project definition
├── abis/erc20.json # ERC20 ABI used by the workflow
├── docker-compose.yml # postgres on localhost:5448
└── .env # DATABASE_URL + RPC url, prefilledThe scaffolded rflow.yaml is a runnable workflow that alerts on large USDC
transfers on Ethereum mainnet, no credentials needed:
rflow_version: 1
name: transfer-alert
config:
port: 3940
db_connection: ${DATABASE_URL}
max_concurrent_runs: 64
networks:
- name: ethereum
chain_id: 1
rpc: ${ETH_RPC}
contracts:
USDC:
abi: ./abis/erc20.json
addresses:
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
notifications:
channels:
ops:
console: {} # alerts print straight into `rflow start` output
workflows:
large-transfer-alert:
trigger:
event:
contract: USDC
name: Transfer
network: ethereum
# fires only above 100000 whole tokens
where: "${{ trigger.args.value > wei('100000', 6) }}"
confirmations: 0
start_block: latest
end_block: live
steps:
- id: alert
notify:
channel: ops
message: "large transfer: ${{ format_units(trigger.args.value, 6) }} USDC in ${{ trigger.tx_hash }}"
on_failure: dead_letterThe scaffolded .env points at the compose Postgres and a free public RPC,
nothing to fill in:
DATABASE_URL=postgresql://postgres:rflow@localhost:5448/postgres
ETH_RPC=https://mainnet.gateway.tenderly.coThe console channel needs no setup; when you want alerts somewhere real,
swap it for telegram, slack, discord, email and friends.
Start Postgres (and anvil, for a local chain)
docker compose up -dRunning against mainnet works as-is. To trigger events yourself, run a local anvil chain instead:
anviland point the network at it in rflow.yaml:
rflow_version: 1
name: transfer-alert
config:
port: 3940
db_connection: ${DATABASE_URL}
max_concurrent_runs: 64
networks:
- name: ethereum
chain_id: 31337
rpc: http://localhost:8545
contracts:
USDC:
abi: ./abis/erc20.json
addresses:
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
...Then deploy any ERC20 to anvil and put its address under
contracts.USDC.addresses.ethereum.
Check your local dependencies are ready before booting:
rflow doctor # Docker, Foundry, Postgres, Node + the rflow.yaml summarydoctor is informational (it always exits 0) and prints a one-line fix hint for
anything that is missing. See Scaffolding.
Start rflow
rflow startrflow validates the config (strict: unknown keys are hard errors, and it collects
all errors before reporting), applies the rflow schema to Postgres, boots the
engines lazily (no signer here, so no relayer boots), and tails the chain from
start_block: latest.
Trigger it
On anvil, fire a qualifying transfer with cast:
cast send <token-address> "transfer(address,uint256)" \
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 200000000000 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80Watch the run land:
rflow ls # workflows: state, trigger, cursor, runs today
rflow runs list # the journal: per-step status, outputs, attemptsSend the same event twice (a reorged duplicate, a replayed block range) and rflow skips it. The trigger key is claimed exactly once. That's the point.
Next steps
- Add a signer and relayers to send transactions
- Rehearse a workflow against history with
rflow replay, or a single fixture withrflow test - Learn the expression language
- Understand the exactly-once guarantees
- Migrating?
rflow import defender ./serverless.yml/rflow import gelato ./tasks.jsonscaffold a project from what you have. See Migrate - Let your editor or AI agent operate the project:
rflow mcpandrflow schema