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

What is rflow?

rflow is an open-source, self-hosted onchain workflow engine. You describe automations in one rflow.yamlwhen 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.

The positioning in one sentence: GitHub-Actions-shaped YAML gets exactly-once, reorg-aware, simulation-gated onchain automation — self-hosted, any custody, any EVM chain.

One binary, three engines

rflow embeds two battle-tested Rust libraries in a single process:

ConcernOwner
Block fetching, event decoding, reorg detection, factory tracking, historical backfillrindexer (embedded)
Nonce management, gas pricing & bumping, rebroadcast, tx expiry, signing (9 providers), allowlists, automatic top-uprrelayer (embedded)
Workflow state machine, exactly-once journal, expressions, simulation gate, approval gates, sagas, backtesting, notifications, HTTP, cursors, CLI, MCPrflow

Events flow in through the embedded indexer, expressions and the step journal run in-process, and transactions go out through the embedded relayer — all function calls, no HTTP hop on the hot path. State lives in a single Postgres database (schemas: rflow, relayer, rindexer).

The engines are lazy: the relayer only boots if you declare a signer/relayers block, and the indexer only boots if you have chain triggers. A monitoring-only project needs no keys; a cron → HTTP → Telegram project needs no networks at all.

What a workflow looks like

workflows:
  mirror:
    trigger:
      event:
        contract: USDC
        name: Transfer
        network: ethereum
        where: "${{ trigger.args.value > wei('1000', 6) }}"
        confirmations: 12
    steps:
      - id: gate
        read:
          contract: USDC
          network: base
          function: "balanceOf(address)"
          args: ["${{ relayers.payout.address }}"]
          assert: "${{ output >= trigger.args.value }}"
      - id: mirror
        send_transaction:
          network: base
          relayer: payout
          contract: USDC
          function: "transfer(address,uint256)"
          args: ["${{ trigger.args.to }}", "${{ trigger.args.value }}"]
          valid_for: 5m
          wait_for: confirmed
      - id: report
        notify:
          channel: ops
          message: "mirrored ${{ format_units(trigger.args.value, 6) }} USDC — ${{ steps.mirror.tx.hash }}"
    on_failure: dead_letter

Steps run strictly sequentially. Any prior step's output is addressable from any later step through the expression language. Sends are pre-flight simulated by default and carry an idempotency key, so a crash mid-run can never double-spend — see Reliability.

What rflow is not

  • Not a hosted service. You run it — your infra, your keys (or your KMS/Privy/ Turnkey/Fireblocks — any of rrelayer's 9 signing providers), your Postgres.
  • Not a smart-contract framework. rflow reacts to chains and sends transactions; it does not deploy or manage contracts.
  • Not an indexer replacement. If you want a full indexing platform (GraphQL APIs, custom schemas, non-workflow consumers), use rindexer directly. rflow uses indexing as a trigger surface — and lets workflows make decisions over the event tables it already writes via query: (with index_events to index extra contracts for exactly that) — but its tables serve workflows, not apps.