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

constants, secrets & lists

Three ways to get values into your expressions.

constants

Static values, addressable as ${{ constants.<name> }}:

constants:
  scale_bps: "2500"
  treasury: "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326"
args: ["${{ mul_div(trigger.args.amount0, constants.scale_bps, 10000) }}"]

secrets

Like constants, but log-redacted: anything under secrets: never appears in rflow's logs or journal output. Addressable as ${{ secrets.<name> }}.

secrets:
  HOOK_KEY: ${BACKEND_HMAC_KEY}

Note the two layers here: ${BACKEND_HMAC_KEY} is plain environment substitution at load time; ${{ secrets.HOOK_KEY }} is the expression that reads it at run time. The typical consumers are an http_call HMAC and a webhook trigger's inbound verification:

- id: notify-backend
  http_call:
    url: ${BACKEND_URL}
    hmac: "${{ secrets.HOOK_KEY }}"
    body: { tx: "${{ trigger.tx_hash }}" }

from — external secret providers

Instead of a ${VAR} string, a secret's value may be a from: reference that rflow fetches from AWS Secrets Manager or GCP Secret Manager at boot — so production API keys never live in .env or the config file:

secret_providers:
  aws-prod:
    aws_secrets_manager:
      region: eu-west-1
      prefix: /rflow/prod/
 
secrets:
  PRICE_API_KEY:
    from:
      provider: aws-prod
      key: price-api-key
  HOOK_KEY: ${BACKEND_HMAC_KEY}   # both forms coexist

Resolution is fail-closed (a missing/denied secret aborts the boot with the provider + key named) and a fetched value redacts identically to a declared one, everywhere. .env stays the zero-config default — a project with no secret_providers: behaves exactly as before. Full provider config, IAM pointers and the rflow secrets ls|doctor commands: external secret providers.

lists

Runtime-mutable watchlists, backed by Postgres. Seed them in YAML and use them with the in operator:

lists:
  watched_traders:
    seed: ["0xWhale1...", "0xWhale2..."]
trigger:
  event:
    contract: AnyPool
    name: Swap
    network: ethereum
    where: "${{ trigger.args.sender in lists.watched_traders }}"

Membership checks against address lists are case-insensitive — see address semantics.

FieldDescription
seedInitial members, owned by the YAML
sourceRefresh members from http: / sql: / file: on an interval — see below

A list's effective membership is the union of the seed, runtime members (written by rflow list add and list_add: / list_remove: steps), and source-refreshed members. rflow list show breaks members down by provenance. Mutations are visible to the very next where: evaluation.

source — http / sql / file

lists:
  watched_traders:
    seed: ["0xWhale1..."]
    source:
      http:
        url: https://api.example.com/traders
        json_path: "$.data[*].address"   # default $ — the body is already an array
        refresh: 5m                      # default 5m
 
  vip_vaults:
    source:
      sql: "SELECT address FROM analytics.vip_vaults"   # single read-only SELECT, same Postgres
 
  blocked:
    source:
      file: ./blocked.txt               # newline-separated, # comments skipped

A refresh atomically replaces only the source-tagged rows — seed, manual and workflow members survive every refresh. Values are trimmed, deduplicated and address-shaped entries lowercased.