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_transactionworkflows 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-alertinto them
Generate it
rflow new --template relayer-low-balance-alert
# or into an existing project:
rflow add workflow relayer-low-balance-alertNon-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=5mThe 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_letterInputs
| key | type | default |
|---|---|---|
project_name | string | relayer-low-balance-alert |
network / chain_id / rpc_env / rpc_url | network / chain_id / env_var / string | ethereum / 1 / ETH_RPC / a public RPC |
relayer_name / relayer_address | string / address | hot-wallet / required |
min_balance | decimal | 0.5 (whole native tokens) |
native_symbol / native_decimals | string / int | ETH / 18 |
poll_interval | duration | 1m |
multicall_address | address | the canonical Multicall3 (same on 250+ chains) |
channel | string | ops |
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 startSet 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_intervalmatched 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 notificationif 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-pagerso 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 setnative_decimalsaccordingly - non-18-decimal natives: set
native_decimals(the condition and the message format through it) - alert into a funding runbook: swap
notify:for anhttp_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