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

# recipe: partial
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 listed in the generated .env.example.

Safety notes

Monitor-only: no signer:, no relayers: β€” 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