Proxy upgrade alert
Template: proxy-upgrade-alert ยท category: security ยท risk: monitor_only
Watches an ERC1967 proxy for Upgraded(address implementation) and notifies
with the new and previous implementation address. The previous one comes
from rflow's durable state, written each time an upgrade fires โ no archive
node needed.
When to use it
- you depend on an upgradeable contract you do not control (a stablecoin, a bridge, a protocol you integrate)
- you operate upgradeable contracts and want an independent record of every implementation change
- as a supply-chain tripwire: an unexpected upgrade is the strongest possible signal to pause integrations
Generate it
rflow new --template proxy-upgrade-alert
# or add the workflow to an existing project:
rflow add workflow proxy-upgrade-alertNon-interactive (CI/agents):
rflow new --template proxy-upgrade-alert --yes --output ./usdc-watch \
--answer proxy_name=USDC --answer proxy_address=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48The generated workflow
# recipe: partial
workflows:
proxy-upgrade-alert:
trigger:
event:
contract: USDC
name: Upgraded
network: ethereum
confirmations: 0
start_block: latest
end_block: live
steps:
# the old implementation comes from durable state written on the
# previous upgrade - 'unknown' on the very first one rflow sees
- id: alert
notify:
channel: ops
message: "PROXY UPGRADED: USDC implementation is now ${{ trigger.args.implementation }} (was ${{ state['proxy-upgrade-alert.last_impl'] if state['proxy-upgrade-alert.last_impl'] is defined else 'unknown' }}) - tx ${{ trigger.tx_hash }}"
- id: remember
state_set:
key: "proxy-upgrade-alert.last_impl"
value: "${{ trigger.args.implementation }}"
on_failure: dead_letterThe Upgraded(address) signature is shared by OpenZeppelin transparent
proxies, UUPS proxies, and older ZeppelinOS proxies (USDC included), so the
default answers watch mainnet USDC out of the box.
Inputs
| key | type | default |
|---|---|---|
project_name | string | proxy-upgrade-alert |
network / chain_id / rpc_env / rpc_url | network / chain_id / env_var / string | ethereum / 1 / ETH_RPC / a public RPC |
proxy_name / proxy_address | contract / address | USDC mainnet |
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. The
state_set key is namespaced
(proxy-upgrade-alert.last_impl) โ rename it if you run several proxy
watchers in one project, because state keys are global across workflows.
on_failure: dead_letter keeps a failed notification journaled for
rflow dead-letter inspection.
Run it locally
docker compose up -d # postgres on localhost:5448
# fill .env (RPC + telegram credentials)
rflow validate
rflow startRehearse without waiting for a real upgrade (dry-run โ state_set journals
would_set and writes nothing):
rflow test proxy-upgrade-alert --fixture fixtures/upgraded.jsonProduction checklist
-
proxy_addressis the proxy, not the implementation - the proxy actually emits ERC1967
Upgraded(transparent/UUPS/legacy ZeppelinOS do; some bespoke proxies use different events) - telegram credentials in
.env, test withrflow test -
rflow validate --preflightpasses against your RPC
Common modifications
- also watch
AdminChanged/BeaconUpgraded(already in the packaged ABI): add a workflow per event - watch several proxies: one
contracts:entry + one workflow each, with distinct state keys - gate an automated response on the upgrade: pause your own integration with
a
send_transactionstep behind an approval gate