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-monitorNon-interactive (CI/agents):
rflow new --template timelock-monitor --yes --output ./gov-watch \
--answer timelock_address=0xYourTimelockWhat it generates
Three event-triggered workflows over one contracts: entry:
| workflow | event | notifies |
|---|---|---|
timelock-call-scheduled | CallScheduled | decoded target, value, delay, operation id |
timelock-call-executed | CallExecuted | target, value, operation id |
timelock-cancelled | Cancelled | operation 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_letterInputs
| key | type | default |
|---|---|---|
project_name | string | timelock-monitor |
network / chain_id / rpc_env / rpc_url | network / chain_id / env_var / string | ethereum / 1 / ETH_RPC / a public RPC |
timelock_name | contract | Timelock |
timelock_address | address | zero placeholder โ replace it |
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:, 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 startRehearse without a real proposal (dry-run, nothing sent):
rflow test timelock-call-scheduled --fixture fixtures/call-scheduled.jsonProduction checklist
-
timelock_addresspoints at the real TimelockController (not the zero placeholder) - the contract is an OZ
TimelockController(Compound-styleTimelockuses different events โQueueTransaction/ExecuteTransaction) - telegram credentials in
.env, test withrflow test - the channel reaches whoever can actually veto/cancel in time
-
rflow validate --preflightpasses against your RPC
Common modifications
- alert on
MinDelayChangetoo (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_encodecomparisons, or forwardtrigger.args.datato anhttp_callfor off-chain decoding - track open operations in a list:
list_addthe id on schedule,list_removeon execute/cancel