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.yaml (when 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 durable trigger deduplication, idempotent transaction submission, pre-flight simulation and reorg awareness. GitHub-Actions-shaped YAML, any custody, any EVM chain.

The journal resumes completed work without repeating settled steps. HTTP calls, notifications and commands can repeat if a crash interrupts recording their outcome, so consequential external effects need receiver-side idempotency. See the execution guarantees.

One binary, three engines

                       rflow.yaml
                           │
            ┌──────────────┴──────────────┐
            │        rflow (one binary)    │
            │                              │
            │  rindexer ──► workflow ──► rrelayer
            │  events in    engine        txs out
            │               │
            │               ▼
            │           Postgres
            │      (journal: every run,
            │       every step, durable)
            └───────────────┬──────────────┘
                            │
                  any EVM chain(s)

rflow embeds two 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, durable journal, expressions, simulation gate, approval gates, sagas, historical-trigger rehearsal, 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. The hot path is all function calls, with no HTTP hop. 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

rflow_version: 1
name: usdc-mirror
 
config:
  port: 3947
  db_connection: ${DATABASE_URL}
 
networks:
  - name: ethereum
    chain_id: 1
    rpc: ${ETH_RPC}
  - name: base
    chain_id: 8453
    rpc: ${BASE_RPC}
 
signer:
  raw:
    mnemonic: ${RAW_DANGEROUS_MNEMONIC}
 
relayers:
  payout:
    networks: [base]
 
contracts:
  USDC:
    abi: ./abis/erc20.json
    addresses:
      ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
      base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
 
notifications:
  channels:
    ops:
      telegram:
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
 
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 a persisted idempotency key, so recovery reconciles the same relayer submission for a step attempt; see Reliability for the database-retention and external-effect boundaries.

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. For a full indexing platform (GraphQL APIs, custom schemas, non-workflow consumers), use rindexer directly. rflow uses indexing as a trigger surface; workflows can query the event tables it writes via query: (add extra contracts with index_events), but its tables serve workflows, not apps.