relayers
Named wallets your workflows send transactions from. The names are your labels β nothing about them is special, and you never touch a relayer API or manage the nameβkey mapping yourself.
relayers:
payout:
networks: [ethereum, base] # same address on all listed chains
speed: FAST # SLOW | MEDIUM | FAST | SUPER
policy:
max_gas_price: 100 gwei # abort sends when gas is above this
whitelist_receivers: # the only addresses this relayer may send to
- "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326"
treasury:
networks: [ethereum]
import: { id: "b47c..." } # adopt an existing relayer (see below)| Field | Required | Description |
|---|---|---|
networks | β | Networks the relayer exists on β created on the first, cloned to the rest, same address everywhere |
speed | rrelayer transaction speed. Gas bumping/escalation is rrelayer's job | |
policy | Per-relayer guard rails, checked by rflow on every send from this relayer β see below | |
import | Adopt an existing relayer by rrelayer uuid |
policy β per-relayer guard rails
policy.max_gas_price (a wei literal, validated statically) and
policy.whitelist_receivers apply to every send from this relayer, whatever
workflow issues it.
Lifecycle
Wallet creation is rflow's job and it is seamless:
-
You declare a signing provider (the key material) and named relayers (pure labels).
-
At first
rflow start(or standalone viarflow relayers sync), rflow reconciles: each name missing from its mapping table is created through the embedded rrelayer admin client (rrelayer namerflow/<name>) on its first listed network, then cloned to the rest β clones reuse the wallet index, which is why the address is identical cross-chain. -
The mapping
(name β relayer id per chain, address)is persisted in therflowschema β stable forever after. -
First boot prints a funding table:
payout β 0xAbc... on ethereum, base β fund merflow relayers ls/rflow relayers balanceshow it any time, and funding operations below cover estimating, funding and auditing balances. Ongoing funding can be automated with theautomatic_top_uppassthrough.
Renames are handled honestly
The YAML key is the identity, so renaming it in the file would create a new wallet. rflow detects the pattern β a previously-mapped name gone plus a new unmapped name β and tells you before creating anything:
'payout' is unmapped; orphaned relayer 'hot' still holds funds at 0xAbc...
run `rflow relayers rename hot payout` to keep the wallet, or continue to create a fresh one.rflow relayers rename <old> <new> re-labels the mapping without touching keys.
rflow relayers ls always shows every mapping including orphans, and removing a name
from the YAML never deletes a relayer β rflow validate warns about orphans instead.
Importing existing rrelayer relayers
If you share an rrelayer database with other services (a standalone rrelayer, or another rflow project on the same Postgres), adopt one of its relayers instead of creating a fresh wallet. The guided flow is discover β import β verify:
rflow relayers discover # list every relayer in the shared rrelayer db
rflow relayers import treasury --id b47c1e2a-... --network ethereum
rflow relayers import --interactive # pick from discover, prompt for the name
rflow relayers verify-imports # re-check every import (CI-friendly)discover shows each relayer's uuid, address, network and a signer-compatibility
check β whether the signing provider you configured derives that
relayer's address. Only compatible relayers can be imported.
import writes the block into rflow.yaml through the same comment-preserving
editor as rflow add (your comments/ordering survive), refusing
to clobber an existing relayer of the same name:
relayers:
treasury:
networks: [ethereum]
import:
id: "b47c1e2a-..."The derived-address check runs before anything is written β a signer mismatch
refuses the import loudly, because otherwise every simulation would run as the old
wallet while the engine signs with a different key. The same verification runs
again at every boot (a mismatch is a boot error), and rflow relayers verify-imports runs it standalone: a table of ok / MISMATCH / NOT FOUND per
import, exiting non-zero on any failure so CI can gate on it. rflow doctor also
checks each import's adoption state.
Funding operations
Knowing every address is half the job β the other half is keeping them funded:
rflow relayers balance # balances per chain with low-balance flags (--json)
rflow relayers funding-plan # balance vs estimated need per relayer/network (--json)
rflow relayers qr payout # the address + a terminal QR code to fund from a wallet
rflow relayers topup-plan # which relayers are short, and by how much
rflow relayers topup --from treasury --to "*" --network ethereum --to-target --yes
rflow relayers history --since 30d # recent gas spend + top-up actionsfunding-plan estimates how much native each relayer wants:
targeting money workflows Γ sends-of-headroom Γ safety factor Γ live gas price Γ
typical gas (defaults 50 sends, 3Γ, 150k gas β all overridable with
--sends-per-workflow / --safety-factor / --typical-gas). This is an honest
gas-headroom heuristic, not a value forecast: transfer values are unknowable
statically, so the safety factor is the coarse knob covering them. A relayer no
money workflow targets needs nothing and is never flagged.
topup funds shortfalls from a funding relayer as normal relayer sends: the
plan prints first, --yes is required to broadcast, and the command refuses when
the funding relayer is itself underfunded or cannot cover the total. Every top-up
lands in the native spend ledger (visible in
rflow spend and the explorer) plus an rflow.operator_audit row, so
rflow relayers history shows who topped up whom.
For automated funding, the automatic_top_up
network passthrough lets rrelayer itself top relayers up from a funding relayer or
Safe β the relayer-underfunded lint rule
nudges money-moving projects toward it, and rflow doctor warns live when a
relayer's balance drops below the funding-plan estimate. The explorer's
Relayers view (backed by GET /api/relayers) shows the same address book:
balances, low-balance flags, funding estimates and recent spend.
Using relayers
- Send steps reference them by name:
send_transaction: { relayer: payout, ... } - Expressions can read the address:
${{ relayers.payout.address }} - Workflows can be restricted to specific relayers via
permissions