Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Quickstart

From nothing to a firing workflow in five commands.

1. Install

curl -L https://rflow.xyz/install.sh | bash

2. Scaffold a project

mkdir transfer-alert && cd transfer-alert
rflow new

rflow new scaffolds from the template registry: a picker lists 17 production-shaped recipes (id + risk + summary — browse them any time with rflow templates ls), then asks one typed prompt per template input. --template <id> pre-selects one; --yes skips the prompts entirely (the large-transfer-alert template with its defaults — what this page follows). Grow the project later with rflow add workflow <template-id> or rflow add network|contract|relayer|notification, which append to rflow.yaml without disturbing your comments or formatting (only a chain + address? rflow contract add fetches the verified ABI for you). It writes:

transfer-alert/
├── rflow.yaml                    # the project definition
├── abis/erc20.json               # ERC20 ABI used by the workflow
├── docker-compose.yml            # postgres:16 on localhost:5448
├── .env                          # DATABASE_URL, RPC + notification credentials
├── .env.example                  # the same keys, empty — safe to commit
├── .rflow/template-lock.yaml     # which template + answers generated this
└── .gitignore                    # ignores .env and .rflow/

The scaffolded rflow.yaml is a real, runnable workflow — alert on large USDC transfers on Ethereum mainnet:

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:
      telegram:
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
 
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_letter

The scaffolded .env points at the compose Postgres and a public RPC:

DATABASE_URL=postgresql://postgres:rflow@localhost:5448/postgres
POSTGRES_PASSWORD=rflow
ETH_RPC=https://eth.merkle.io
TG_BOT_TOKEN=
TG_CHAT_ID=

Fill in TG_BOT_TOKEN/TG_CHAT_ID (any Telegram bot token + chat), or swap the channel for slack: { webhook_url: ... } / discord: { webhook_url: ... }.

3. Start Postgres (and anvil, for a local chain)

docker compose up -d

Running against mainnet works as-is. To trigger events yourself, run a local anvil chain instead:

anvil

and point the network at it in rflow.yaml:

networks:
  - name: ethereum
    chain_id: 31337
    rpc: http://localhost:8545

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 summary

doctor is informational (it always exits 0) and prints a one-line fix hint for anything that is missing. See Scaffolding.

4. Start rflow

rflow start

rflow 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.

5. Trigger it

On anvil, fire a qualifying transfer with cast:

cast send <token-address> "transfer(address,uint256)" \
  0x70997970C51812dc3A010C7d01b50e0d17dc79C8 200000000000 \
  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Watch the run land:

rflow ls          # workflows: state, trigger, cursor, runs today
rflow runs list   # the journal: per-step status, outputs, attempts

Send 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