Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
- **Logging**: Use structured logging instead of print statements
- **Testing**: Write unit tests for utility functions and integration tests for protocols

## Monitoring Metadata

- `monitoring.yaml` at the repo root is the source of truth for the protocol cards on https://curation.yearn.fi/monitoring/.
- When adding, removing, or changing a monitor, update `monitoring.yaml` and the protocol `README.md`.
- The alerts API exposes this metadata at `GET /v1/monitoring` (see `api/server.py`).
- `tests/test_monitoring_config.py` validates that every enabled protocol task in `automation/jobs.yaml` has a `monitoring.yaml` entry and that every task referenced there is scheduled.

## Best Practices
- Create custom exception types for domain-specific errors
- Extract repeated patterns into utility functions. Try to keep utility functions small and focused on a single task and store in utils folder.
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,10 @@ write_last_value_to_file(cache_filename, "MY_KEY", new_value)

1. Create `protocols/protocol-name/main.py` following the pattern above, with a `main()` function and an `if __name__ == "__main__":` block that wraps it via `run_with_alert(main, PROTOCOL)` (see [Script Entrypoint](#script-entrypoint)). Reference ABIs by their repo-root-relative path, e.g. `load_abi("protocols/protocol-name/abi/Foo.json")`
2. Add a `protocols/protocol-name/README.md` describing what it monitors
3. No `pyproject.toml` change is needed — packages under `protocols/` are discovered automatically (see `[tool.setuptools.packages.find]`)
4. Add the corresponding `TELEGRAM_BOT_TOKEN_*` and `TELEGRAM_CHAT_ID_*` entries to `.env.example`
5. Register the script in `automation/jobs.yaml` under the right profile if it should run on a schedule
3. Add or update the protocol entry in `monitoring.yaml` so the curation website card stays in sync
4. No `pyproject.toml` change is needed — packages under `protocols/` are discovered automatically (see `[tool.setuptools.packages.find]`)
5. Add the corresponding `TELEGRAM_BOT_TOKEN_*` and `TELEGRAM_CHAT_ID_*` entries to `.env.example`
6. Register the script in `automation/jobs.yaml` under the right profile if it should run on a schedule

## Tests

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ uv run protocols/aave/main.py

In production the scripts run on a schedule via supercronic on a VPS, defined in [`automation/jobs.yaml`](./automation/jobs.yaml). See [`deploy/`](./deploy/) — [`install.sh`](./deploy/install.sh) provisions a host and [`runbook.md`](./deploy/runbook.md) covers operations.

The optional read-only alerts API exposes persisted alert history from SQLite. See [`deploy/alerts-api.md`](./deploy/alerts-api.md) for endpoint examples and pagination.
The optional read-only alerts API exposes persisted alert history from SQLite and monitoring card metadata. See [`deploy/alerts-api.md`](./deploy/alerts-api.md) for endpoint examples and pagination, or `GET /v1/monitoring` for the protocol card data used by the curation website.

## Code Style

Expand Down
4 changes: 4 additions & 0 deletions api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from automation.config import JobsConfig, load_jobs_config
from utils.logger import get_logger
from utils.monitoring_config import load_monitoring_config, monitoring_to_json
from utils.store import AlertEvent, get_alert, normalize_timestamp, query_alerts

logger = get_logger("api.server")
Expand Down Expand Up @@ -160,6 +161,9 @@ def do_GET(self) -> None:
if parsed.path == "/v1/protocols":
write_json(self, 200, protocols_to_json(load_jobs_config()))
return
if parsed.path == "/v1/monitoring":
write_json(self, 200, monitoring_to_json(load_monitoring_config()))
return
if parsed.path == "/v1/alerts":
alert_query = parse_alert_query(parsed.query)
rows = query_alerts(
Expand Down
35 changes: 35 additions & 0 deletions deploy/alerts-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,41 @@ Example response:
}
```

### `GET /v1/monitoring`

Returns the structured protocol card metadata from `monitoring.yaml`, used by
the curation website to render what each protocol monitors.

```sh
curl http://127.0.0.1:8923/v1/monitoring
```

Example response:

```json
{
"version": "1.0",
"data": [
{
"slug": "3jane",
"display_name": "3Jane",
"description": "USD3/sUSD3 credit market monitoring on Mainnet",
"cadence": "Hourly",
"monitor_count": 11,
"disabled": false,
"tasks": ["protocols/3jane/main.py"],
"monitors": [
{
"name": "Price Per Share",
"description": "USD3 PPS decrease (any drop vs cached prior, CRITICAL) and sUSD3 PPS decrease (HIGH)"
}
]
}
],
"count": 1
}
```

## Delivery Status

An alert row means a monitor generated an alert. Telegram delivery is tracked
Expand Down
Loading