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

Timelock monitor

Template: timelock-monitor ยท category: governance ยท risk: monitor_only

Watches an OpenZeppelin TimelockController and notifies when an operation is scheduled (with decoded target, value, and delay), executed, or cancelled. The scheduled alert is the important one: the delay window is the whole point of a timelock, and this template pages you while there is still time to react.

When to use it

  • your protocol's admin functions sit behind a timelock and you want every queued operation in front of humans immediately
  • you are a token holder / integrator watching someone else's governance
  • as part of an incident-response plan: "who watches the timelock?" โ€” this does

Generate it

rflow new --template timelock-monitor

Non-interactive (CI/agents):

rflow new --template timelock-monitor --yes --output ./gov-watch \
  --answer timelock_address=0xYourTimelock

What it generates

Three event-triggered workflows over one contracts: entry:

workfloweventnotifies
timelock-call-scheduledCallScheduleddecoded target, value, delay, operation id
timelock-call-executedCallExecutedtarget, value, operation id
timelock-cancelledCancelledoperation id
# recipe: partial
workflows:
  timelock-call-scheduled:
    trigger:
      event:
        contract: Timelock
        name: CallScheduled
        network: ethereum
        confirmations: 0
        start_block: latest
        end_block: live
    steps:
      - id: alert
        notify:
          channel: ops
          message: "timelock SCHEDULED on Timelock: target ${{ trigger.args.target }}, value ${{ format_units(trigger.args.value, 18) }} native, executable in ${{ trigger.args.delay }}s - id ${{ trigger.args.id }} tx ${{ trigger.tx_hash }}"
    on_failure: dead_letter

Inputs

keytypedefault
project_namestringtimelock-monitor
network / chain_id / rpc_env / rpc_urlnetwork / chain_id / env_var / stringethereum / 1 / ETH_RPC / a public RPC
timelock_namecontractTimelock
timelock_addressaddresszero placeholder โ€” replace it
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:, no transactions. Batched timelock operations emit one CallScheduled per call (same id, increasing index) โ€” a big batch produces several notifications, which is usually what you want for governance review. Every workflow ends in on_failure: dead_letter.

Run it locally

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

Rehearse without a real proposal (dry-run, nothing sent):

rflow test timelock-call-scheduled --fixture fixtures/call-scheduled.json

Production checklist

  • timelock_address points at the real TimelockController (not the zero placeholder)
  • the contract is an OZ TimelockController (Compound-style Timelock uses different events โ€” QueueTransaction/ExecuteTransaction)
  • telegram credentials in .env, test with rflow test
  • the channel reaches whoever can actually veto/cancel in time
  • rflow validate --preflight passes against your RPC

Common modifications

  • alert on MinDelayChange too (already in the packaged ABI) โ€” a shrinking delay is itself a governance event worth paging on
  • decode the scheduled calldata further in the message with abi_encode comparisons, or forward trigger.args.data to an http_call for off-chain decoding
  • track open operations in a list: list_add the id on schedule, list_remove on execute/cancel