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

Relayer low balance alert

Template: relayer-low-balance-alert · category: relayers · risk: monitor_only

Polls a relayer wallet's native balance and alerts a channel once when it crosses under a floor. The read goes through Multicall3's getEthBalance(address) view (deployed at the same address on 250+ chains), so no custom contract is needed. No signer, no relayer engine, just a read trigger over eth_call.

When to use it

  • the wallet that funds your send_transaction workflows must never run dry; get paged before sends start failing
  • watch any hot wallet, keeper EOA, or gas tank on any EVM chain
  • pair it with the projects that actually send: rflow add workflow relayer-low-balance-alert into them

Generate it

rflow new --template relayer-low-balance-alert
# or into an existing project:
rflow add workflow relayer-low-balance-alert

Non-interactive (CI/agents):

rflow new --template relayer-low-balance-alert --yes --output ./low-balance \
  --answer relayer_address=0x9c5083dd4838e120dbeac44c052179692aa5dac5 \
  --answer min_balance=1.5 --answer poll_interval=5m

The generated YAML

The full generated rflow.yaml, with the workflow itself highlighted:

# recipe: partial
rflow_version: 1
name: relayer-low-balance-alert
 
config:
  port: 3940
  db_connection: ${DATABASE_URL}
 
networks:
  - name: ethereum
    chain_id: 1
    rpc: ${ETH_RPC}
 
contracts:
  # the canonical Multicall3 - the same address on 250+ chains
  Multicall3:
    abi: ./abis/multicall3.json
    network: ethereum
    address: "0xcA11bde05977b3631167028862bE2a173976CA11"
 
notifications:
  channels:
    ops:
      telegram:
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
 
workflows: 
  relayer-low-balance-alert: 
    trigger: 
      read: 
        contract: Multicall3
        network: ethereum
        function: "getEthBalance(address)"
        args: ["0x9c5083dd4838e120dbeac44c052179692aa5dac5"] 
        every: 1m
        condition: "${{ output < wei('0.5', 18) }}"
        mode: threshold
    steps: 
      - id: alert
        notify: 
          channel: ops
          message: "relayer hot-wallet (0x9c50...dac5) is under the 0.5 ETH floor: ${{ format_units(trigger.args.output, 18) }} ETH left - top it up before sends start failing"
    on_failure: dead_letter

Inputs

keytypedefault
project_namestringrelayer-low-balance-alert
network / chain_id / rpc_env / rpc_urlnetwork / chain_id / env_var / stringethereum / 1 / ETH_RPC / a public RPC
relayer_name / relayer_addressstring / addresshot-wallet / required
min_balancedecimal0.5 (whole native tokens)
native_symbol / native_decimalsstring / intETH / 18
poll_intervalduration1m
multicall_addressaddressthe canonical Multicall3 (same on 250+ chains)
channelstringops

Required env vars

DATABASE_URL, the RPC env var (default ETH_RPC), TG_BOT_TOKEN, TG_CHAT_ID. All are listed in the generated .env.example.

Safety notes

Monitor-only: no signer:, no relayers:, so the relayer engine never boots. The trigger's threshold mode is edge-triggered and persisted (rflow.read_trigger_state): one alert on the crossing under the floor, not one per poll while the balance stays low, and a restart mid-"low" does not re-page. The alert path ends in on_failure: dead_letter, so a failed notification is journaled, never lost.

Run it locally

docker compose up -d     # postgres on localhost:5448
# fill .env (RPC + telegram credentials)
rflow validate
rflow start

Set min_balance above the wallet's real balance and the alert fires on the first poll (a false→true crossing from the trigger's initial state); fixtures/low-balance-fire.json documents the fire payload the message renders against.

Production checklist

  • floor covers your worst gas day: min_balance ≥ (sends/day × gas/send × a safety factor). An alert that fires after the last send fails is decoration
  • poll_interval matched to burn rate (1m for busy keepers, 15m for a quiet treasury)
  • the alert lands on a channel someone acts on: pagerduty via rflow add notification if telegram is too quiet
  • one watcher per funded wallet: repeat rflow add workflow relayer-low-balance-alert --name <wallet> --answer relayer_address=0x...
  • add workflow-error-pager so a dead-lettered alert run also pages

Common modifications

  • watch an ERC20 gas token instead: point the contract at the token with balanceOf(address) and set native_decimals accordingly
  • non-18-decimal natives: set native_decimals (the condition and the message format through it)
  • alert into a funding runbook: swap notify: for an http_call: to your ops automation, or graduate to a money-moving top-up workflow behind an approval gate
  • add a second, lower "critical" floor as another workflow with a pagerduty channel