Skip to main content

ownerwatch

ci release license

Self-hosted monitor that alerts you the moment the admin surface of your EVM contracts changes.

Most protocol losses in 2025-2026 started with a key, a role or a proxy changing hands, not with a clever exploit. ownerwatch watches the events that mean "someone just gained control" and pushes them to Discord, Slack, Telegram or any webhook within seconds. No account, no SaaS, no third party holding your contract list. One YAML file and a Docker container.

What it watches

Every address in your config gets the full catalog:

Standard Events Severity
Ownable / Ownable2Step OwnershipTransferred, OwnershipTransferStarted critical / high
AccessControl RoleGranted, RoleRevoked, RoleAdminChanged high / critical
ERC-1967 proxies Upgraded, AdminChanged, BeaconUpgraded critical
Pausable Paused, Unpaused medium
TimelockController CallScheduled, CallExecuted, Cancelled, MinDelayChange high / critical
Legacy Timelock (Compound, Uniswap and forks) NewAdmin, NewPendingAdmin, NewDelay, QueueTransaction, ExecuteTransaction, CancelTransaction medium to critical
Governor (OpenZeppelin and GovernorBravo) ProposalCreated, ProposalQueued, ProposalExecuted, ProposalCanceled, VotingDelaySet, VotingPeriodSet, ProposalThresholdSet, QuorumNumeratorUpdated, TimelockChange, NewImplementation medium to critical
Aragon OS (Lido and others) SetApp, SetPermission, ChangePermissionManager, StartVote, ExecuteVote medium to critical
Safe AddedOwner, RemovedOwner, ChangedThreshold, EnabledModule, DisabledModule, ChangedGuard, ChangedFallbackHandler, ExecutionFromModuleSuccess, ExecutionSuccess, ExecutionFailure info to critical
ERC-20 treasuries Transfer out above a threshold you set high
NFT treasuries Any ERC-721 or ERC-1155 transfer out high
Native balance ETH balance of a treasury falls by more than a threshold between polls high

Safe queues. Add a Safe under safes: and ownerwatch polls the Safe Transaction Service: you get an alert when a transaction is proposed, and a critical one when it has collected enough signatures to execute. That is the moment to act; the on-chain events above are the record of what already happened.

Timelock and Safe-queue alerts show the call being made: full function and arguments when the target is verified on Sourcify, otherwise the signature from the openchain and 4byte databases, otherwise the raw selector. Set explorer_url and every alert carries a clickable transaction link.

Run ownerwatch events to print the catalog.

Quick start

pip install git+https://github.com/imal1ice/ownerwatch
cp config.example.yaml config.yaml   # add your contracts and a Discord/Telegram target
ownerwatch run --config config.yaml

Or with Docker:

docker compose up -d        # uses ./config.yaml, keeps state in a named volume

A hardened systemd unit is in deploy/ownerwatch.service.

Several chains in one process: put shared settings at the top level and a chains: list underneath, see examples/multichain.yaml.

Don't know what to watch? Point probe at your main contract and it walks the owner/admin/proxy chain, tells you who can upgrade or pause it, flags EOAs and 1-of-N Safes, and prints a config:

ownerwatch probe --rpc https://eth.drpc.org --safe-chain eth --label Acme 0xYourProxy 0xYourSafe
ownerwatch probe --rpc https://eth.drpc.org --roles-from-logs 0xAccessControlContract   # every current role holder, replayed from logs since deployment
ownerwatch probe --config config.yaml --markdown --label Acme > admin-surface.md   # monthly report of everything you watch

--roles-from-logs finds the creation block on Sourcify and reads the history from a free public log gateway on Ethereum, Optimism, Arbitrum, Gnosis and Polygon; on Base (and for any chain without one) pass --logs-rpc with an RPC that serves wide eth_getLogs ranges, or --from-block to bound the scan.

Want that check in CI? examples/github-action-drift.yml re-probes weekly and fails when a signer, delay or proxy admin changes from the committed report.

</code></pre>
<p>Other commands:</p>
<pre><code class="language-bash">ownerwatch validate                         # check the config, the RPC and that every address has code
ownerwatch test-alert                       # push a test message through your sinks
ownerwatch run --once                       # single scan, for cron
ownerwatch backfill --from-block N --to-block M   # replay history, prints what you would have seen
ownerwatch events                           # list the catalog

See docs/what-to-watch.md for the control-graph model behind the catalog, and docs/sample-alerts.md for real alerts replayed from mainnet history.

Examples

examples/ has verified configs for Uniswap, Compound, Arbitrum, Aave and the Optimism Foundation Safe on mainnet. Backfill one to see what alerts look like on real history:

ownerwatch backfill --config examples/aave-mainnet.yaml --from-block 22000000 --to-block 22050000

How it works

  • Polls eth_getLogs for the catalog's topic hashes on your addresses, in chunks that fit public RPCs.
  • Waits confirmations blocks behind the head so small reorgs don't produce ghost alerts.
  • Persists a block cursor to state/, so a restart resumes where it left off with no duplicates.
  • Alert delivery never stops the poller: a dead webhook is logged and skipped.
  • Set heartbeat_url to a dead-man's-switch (healthchecks.io or similar) and you get paged when ownerwatch itself stops polling. Quiet and dead must not look the same.
  • Works on any EVM chain, one process can watch several.

Hosted tier

Don't want to run it? I run it for you, per chain, paid in USDC on-chain through contracts/src/Subscriptions.sol: approve, call subscribe(periods, chains, maxCost, configRef), send me the config. No invoices, no accounts, no KYC either way. Details in docs/hosted.md. Deployment address will be listed here once live; until then open an issue. The operator side is ownerwatch hosted --rpc ... --subscriptions 0x... --configs configs/: it starts a watcher for every address with an active subscription and stops it when the subscription lapses.

cd contracts && forge install foundry-rs/forge-std --no-git && forge test

Roadmap

  • Native balance and NFT alerts for Safe queues (currently on-chain only)

Support the project

ownerwatch is free and MIT licensed. If it saves you a 3am incident, fund the work:

Need it deployed, extended or integrated with your alerting stack? Open an issue or reach out on GitHub.

Development

python3 -m venv .venv && .venv/bin/pip install -e . pytest
.venv/bin/pytest        # end-to-end test against a local anvil node; needs foundry

Metadata

Release files for ownerwatch 0.7.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ownerwatch 0.7.2
File Size Uploaded Provenance
ownerwatch-0.7.2.tar.gz 42.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ownerwatch 0.7.2
File Interpreter ABI Platform
ownerwatch-0.7.2-py3-none-any.whl Python 3 none any Details

Total release size: 78.2 kB

Release files / ownerwatch-0.7.2.tar.gz

Download URL ownerwatch-0.7.2.tar.gz
Size 42.6 kB
Tags Source
SHA-256 checksum
How to use checksums
72eac2db360414d13e44723e7ddfc6902551d5209dd2655ae738b56dab33ff45
BLAKE2b-256 checksum
How to use checksums
6787613646160c613f68c65b43b9b9da1661b1acbad3ad8350c3bc37f430d764
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / ownerwatch-0.7.2-py3-none-any.whl

Download URL ownerwatch-0.7.2-py3-none-any.whl
Size 35.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6b53a38da5e582e62b28db70f8630d592e113c9f34d4ff884826d420f9e7afe0
BLAKE2b-256 checksum
How to use checksums
9939dca5d11f75ae2471796820fa2612744097db489720741b23e29451fae3f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for ownerwatch 0.7.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ownerwatch-0.7.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.7.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page