csm-dashboard 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csm_dashboard-0.2.0/.dockerignore +75 -0
- csm_dashboard-0.2.0/.env.example +16 -0
- csm_dashboard-0.2.0/.github/workflows/docker-publish.yaml +48 -0
- csm_dashboard-0.2.0/.github/workflows/release.yaml +88 -0
- csm_dashboard-0.2.0/.gitignore +37 -0
- csm_dashboard-0.2.0/Dockerfile +55 -0
- csm_dashboard-0.2.0/PKG-INFO +354 -0
- csm_dashboard-0.2.0/README.md +334 -0
- csm_dashboard-0.2.0/abis/CSAccounting.json +37 -0
- csm_dashboard-0.2.0/abis/CSFeeDistributor.json +69 -0
- csm_dashboard-0.2.0/abis/CSModule.json +55 -0
- csm_dashboard-0.2.0/abis/stETH.json +42 -0
- csm_dashboard-0.2.0/docker-compose.yml +46 -0
- csm_dashboard-0.2.0/img/csm-dash-cli.png +0 -0
- csm_dashboard-0.2.0/img/csm-dash-web.png +0 -0
- csm_dashboard-0.2.0/img/logo.png +0 -0
- csm_dashboard-0.2.0/pyproject.toml +34 -0
- csm_dashboard-0.2.0/requirements.txt +56 -0
- csm_dashboard-0.2.0/src/__init__.py +1 -0
- csm_dashboard-0.2.0/src/cli/__init__.py +1 -0
- csm_dashboard-0.2.0/src/cli/commands.py +624 -0
- csm_dashboard-0.2.0/src/core/__init__.py +1 -0
- csm_dashboard-0.2.0/src/core/config.py +42 -0
- csm_dashboard-0.2.0/src/core/contracts.py +19 -0
- csm_dashboard-0.2.0/src/core/types.py +153 -0
- csm_dashboard-0.2.0/src/data/__init__.py +1 -0
- csm_dashboard-0.2.0/src/data/beacon.py +370 -0
- csm_dashboard-0.2.0/src/data/cache.py +67 -0
- csm_dashboard-0.2.0/src/data/etherscan.py +78 -0
- csm_dashboard-0.2.0/src/data/ipfs_logs.py +267 -0
- csm_dashboard-0.2.0/src/data/known_cids.py +30 -0
- csm_dashboard-0.2.0/src/data/lido_api.py +35 -0
- csm_dashboard-0.2.0/src/data/onchain.py +258 -0
- csm_dashboard-0.2.0/src/data/rewards_tree.py +58 -0
- csm_dashboard-0.2.0/src/data/strikes.py +214 -0
- csm_dashboard-0.2.0/src/main.py +39 -0
- csm_dashboard-0.2.0/src/services/__init__.py +1 -0
- csm_dashboard-0.2.0/src/services/operator_service.py +320 -0
- csm_dashboard-0.2.0/src/web/__init__.py +1 -0
- csm_dashboard-0.2.0/src/web/app.py +576 -0
- csm_dashboard-0.2.0/src/web/routes.py +161 -0
- csm_dashboard-0.2.0/tests/__init__.py +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Git
|
|
2
|
+
.git
|
|
3
|
+
.gitignore
|
|
4
|
+
.github
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
pip-log.txt
|
|
29
|
+
pip-delete-this-directory.txt
|
|
30
|
+
.tox/
|
|
31
|
+
.coverage
|
|
32
|
+
.coverage.*
|
|
33
|
+
.cache
|
|
34
|
+
nosetests.xml
|
|
35
|
+
coverage.xml
|
|
36
|
+
*.cover
|
|
37
|
+
.hypothesis/
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
|
|
40
|
+
# Virtual environments
|
|
41
|
+
venv/
|
|
42
|
+
env/
|
|
43
|
+
ENV/
|
|
44
|
+
.venv
|
|
45
|
+
|
|
46
|
+
# IDE
|
|
47
|
+
.vscode
|
|
48
|
+
.idea
|
|
49
|
+
*.swp
|
|
50
|
+
*.swo
|
|
51
|
+
*~
|
|
52
|
+
.DS_Store
|
|
53
|
+
|
|
54
|
+
# Environment
|
|
55
|
+
.env
|
|
56
|
+
.env.local
|
|
57
|
+
.env.*.local
|
|
58
|
+
|
|
59
|
+
# Documentation
|
|
60
|
+
docs/
|
|
61
|
+
|
|
62
|
+
# OS
|
|
63
|
+
.DS_Store
|
|
64
|
+
Thumbs.db
|
|
65
|
+
|
|
66
|
+
# Node (if used)
|
|
67
|
+
node_modules/
|
|
68
|
+
npm-debug.log
|
|
69
|
+
yarn-error.log
|
|
70
|
+
|
|
71
|
+
# Other
|
|
72
|
+
.editorconfig
|
|
73
|
+
.pre-commit-config.yaml
|
|
74
|
+
CONTRIBUTING.md
|
|
75
|
+
LICENSE
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Ethereum RPC URL - use your own for better rate limits
|
|
2
|
+
ETH_RPC_URL="https://ethereum-rpc.publicnode.com"
|
|
3
|
+
|
|
4
|
+
# Beacon Chain API (optional, for validator performance data)
|
|
5
|
+
BEACON_API_URL="https://beaconcha.in/api/v1"
|
|
6
|
+
|
|
7
|
+
# beaconcha.in API key (optional, for higher rate limits)
|
|
8
|
+
# Get your API key at https://beaconcha.in/pricing
|
|
9
|
+
# BEACON_API_KEY=your_api_key_here
|
|
10
|
+
|
|
11
|
+
# Etherscan API key (optional, for contract verification and other features)
|
|
12
|
+
# Get your API key at https://etherscan.io/apis
|
|
13
|
+
# ETHERSCAN_API_KEY=your_api_key_here
|
|
14
|
+
|
|
15
|
+
# Cache TTL in seconds (default 5 minutes)
|
|
16
|
+
CACHE_TTL_SECONDS=300
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Build and Push Docker Image
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
tags:
|
|
7
|
+
- 'v*'
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [ main, master ]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
docker:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Docker Buildx
|
|
19
|
+
uses: docker/setup-buildx-action@v3
|
|
20
|
+
|
|
21
|
+
- name: Log in to Docker Hub
|
|
22
|
+
if: github.event_name != 'pull_request'
|
|
23
|
+
uses: docker/login-action@v3
|
|
24
|
+
with:
|
|
25
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
26
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
27
|
+
|
|
28
|
+
- name: Extract metadata
|
|
29
|
+
id: meta
|
|
30
|
+
uses: docker/metadata-action@v5
|
|
31
|
+
with:
|
|
32
|
+
images: 0xdespot/lido-csm-dashboard
|
|
33
|
+
tags: |
|
|
34
|
+
type=ref,event=branch
|
|
35
|
+
type=ref,event=pr
|
|
36
|
+
type=semver,pattern={{version}}
|
|
37
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
38
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
39
|
+
|
|
40
|
+
- name: Build and push
|
|
41
|
+
uses: docker/build-push-action@v5
|
|
42
|
+
with:
|
|
43
|
+
context: .
|
|
44
|
+
push: ${{ github.event_name != 'pull_request' }}
|
|
45
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
46
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
47
|
+
cache-from: type=gha
|
|
48
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write # Required for OIDC trusted publishing
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # Needed for commit history
|
|
20
|
+
|
|
21
|
+
- name: Extract version from tag
|
|
22
|
+
id: version
|
|
23
|
+
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
24
|
+
|
|
25
|
+
- name: Generate release notes
|
|
26
|
+
run: |
|
|
27
|
+
# Get commits since last tag
|
|
28
|
+
LAST_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
|
29
|
+
if [ -n "$LAST_TAG" ]; then
|
|
30
|
+
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s" --no-merges)
|
|
31
|
+
else
|
|
32
|
+
COMMITS=$(git log --pretty=format:"- %s" --no-merges -20)
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Write release notes
|
|
36
|
+
cat > release_notes.md << EOF
|
|
37
|
+
## What's Changed
|
|
38
|
+
|
|
39
|
+
$COMMITS
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
### PyPI
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
pip install csm-dashboard==${{ steps.version.outputs.VERSION }}
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
### Docker
|
|
49
|
+
\`\`\`bash
|
|
50
|
+
docker pull 0xdespot/lido-csm-dashboard:${{ steps.version.outputs.VERSION }}
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
### From Source
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
git clone https://github.com/${{ github.repository }}.git
|
|
56
|
+
cd lido-csm-dashboard
|
|
57
|
+
pip install -e .
|
|
58
|
+
\`\`\`
|
|
59
|
+
EOF
|
|
60
|
+
|
|
61
|
+
- name: Create GitHub Release
|
|
62
|
+
uses: softprops/action-gh-release@v2
|
|
63
|
+
with:
|
|
64
|
+
name: v${{ steps.version.outputs.VERSION }}
|
|
65
|
+
body_path: release_notes.md
|
|
66
|
+
draft: false
|
|
67
|
+
prerelease: ${{ contains(github.ref, '-') }}
|
|
68
|
+
|
|
69
|
+
pypi:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
environment: pypi
|
|
72
|
+
steps:
|
|
73
|
+
- name: Checkout
|
|
74
|
+
uses: actions/checkout@v4
|
|
75
|
+
|
|
76
|
+
- name: Set up Python
|
|
77
|
+
uses: actions/setup-python@v5
|
|
78
|
+
with:
|
|
79
|
+
python-version: '3.11'
|
|
80
|
+
|
|
81
|
+
- name: Install build tools
|
|
82
|
+
run: pip install build
|
|
83
|
+
|
|
84
|
+
- name: Build package
|
|
85
|
+
run: python -m build
|
|
86
|
+
|
|
87
|
+
- name: Publish to PyPI
|
|
88
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Byte-compiled / optimized / compiled files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyd
|
|
5
|
+
*.pyo
|
|
6
|
+
|
|
7
|
+
# Virtual environment
|
|
8
|
+
venv/
|
|
9
|
+
.venv/
|
|
10
|
+
|
|
11
|
+
# Environment variables
|
|
12
|
+
.env
|
|
13
|
+
|
|
14
|
+
# Editor-specific files
|
|
15
|
+
.vscode/ # VS Code specific settings (consider what you want to ignore here)
|
|
16
|
+
|
|
17
|
+
# IDE-specific files
|
|
18
|
+
.idea/ # IntelliJ IDEA related files
|
|
19
|
+
|
|
20
|
+
# OS-specific files
|
|
21
|
+
.DS_Store # macOS
|
|
22
|
+
Thumbs.db # Windows
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
|
|
28
|
+
# Distribution / packaging
|
|
29
|
+
dist/
|
|
30
|
+
build/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
|
|
33
|
+
# Cache directories
|
|
34
|
+
.cache/
|
|
35
|
+
|
|
36
|
+
DESIGN.md
|
|
37
|
+
.DS_Store
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Multi-stage build for optimized Docker image
|
|
2
|
+
FROM python:3.11-slim AS builder
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Install build dependencies
|
|
7
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
8
|
+
build-essential \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
# Copy requirements first for better caching
|
|
12
|
+
COPY requirements.txt ./
|
|
13
|
+
|
|
14
|
+
# Install Python dependencies in a virtual environment
|
|
15
|
+
RUN python -m venv /opt/venv
|
|
16
|
+
ENV PATH="/opt/venv/bin:$PATH"
|
|
17
|
+
RUN pip install --upgrade pip && \
|
|
18
|
+
pip install --no-cache-dir -r requirements.txt
|
|
19
|
+
|
|
20
|
+
# Final stage
|
|
21
|
+
FROM python:3.11-slim
|
|
22
|
+
|
|
23
|
+
WORKDIR /app
|
|
24
|
+
|
|
25
|
+
# Install runtime dependencies only
|
|
26
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
27
|
+
curl \
|
|
28
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
29
|
+
|
|
30
|
+
# Copy virtual environment from builder
|
|
31
|
+
COPY --from=builder /opt/venv /opt/venv
|
|
32
|
+
|
|
33
|
+
# Copy application code and ABIs
|
|
34
|
+
COPY src ./src
|
|
35
|
+
COPY abis ./abis
|
|
36
|
+
|
|
37
|
+
# Create csm alias script
|
|
38
|
+
RUN echo '#!/bin/sh\npython -m src.main "$@"' > /usr/local/bin/csm && \
|
|
39
|
+
chmod +x /usr/local/bin/csm
|
|
40
|
+
|
|
41
|
+
# Set environment variables
|
|
42
|
+
ENV PATH="/opt/venv/bin:$PATH" \
|
|
43
|
+
PYTHONUNBUFFERED=1 \
|
|
44
|
+
PYTHONDONTWRITEBYTECODE=1 \
|
|
45
|
+
PYTHONPATH=/app
|
|
46
|
+
|
|
47
|
+
# Health check
|
|
48
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
|
49
|
+
CMD curl -f http://localhost:3000/api/health || exit 1
|
|
50
|
+
|
|
51
|
+
# Expose port for web dashboard
|
|
52
|
+
EXPOSE 3000
|
|
53
|
+
|
|
54
|
+
# Default command - run the web dashboard using csm alias
|
|
55
|
+
CMD ["csm", "serve", "--host", "0.0.0.0", "--port", "3000"]
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csm-dashboard
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Lido CSM Operator Dashboard for tracking validator earnings
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: fastapi>=0.104
|
|
7
|
+
Requires-Dist: httpx>=0.25
|
|
8
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
9
|
+
Requires-Dist: pydantic>=2.5
|
|
10
|
+
Requires-Dist: python-dotenv>=1.0
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: typer>=0.9
|
|
13
|
+
Requires-Dist: uvicorn>=0.24
|
|
14
|
+
Requires-Dist: web3>=6.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-httpx>=0.21; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Lido CSM Operator Dashboard
|
|
22
|
+
|
|
23
|
+
Track your Lido Community Staking Module (CSM) validator earnings, excess bond, and cumulative rewards.
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- Look up operator by Ethereum address (manager or rewards address) or operator ID
|
|
32
|
+
- View current bond vs required bond (excess is claimable)
|
|
33
|
+
- Track cumulative rewards and unclaimed amounts
|
|
34
|
+
- Detailed validator status from beacon chain (with `--detailed` flag)
|
|
35
|
+
- APY metrics: reward APY, bond APY (stETH rebase), and net APY
|
|
36
|
+
- JSON output for scripting and automation
|
|
37
|
+
- CLI for quick terminal lookups
|
|
38
|
+
- Web interface for browser-based monitoring
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### Option 1: Docker (Recommended)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Clone the repository
|
|
46
|
+
git clone <repo-url>
|
|
47
|
+
cd lido-csm-dashboard
|
|
48
|
+
|
|
49
|
+
# Copy and configure environment
|
|
50
|
+
cp .env.example .env
|
|
51
|
+
|
|
52
|
+
# Start the web dashboard
|
|
53
|
+
docker compose up -d
|
|
54
|
+
|
|
55
|
+
# View logs
|
|
56
|
+
docker compose logs -f
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The web dashboard will be available at http://localhost:3000
|
|
60
|
+
|
|
61
|
+
### Option 2: Local Python Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Clone the repository
|
|
65
|
+
git clone <repo-url>
|
|
66
|
+
cd lido-csm-dashboard
|
|
67
|
+
|
|
68
|
+
# Install with pip
|
|
69
|
+
pip install -e .
|
|
70
|
+
|
|
71
|
+
# Or with uv
|
|
72
|
+
uv pip install -e .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
Copy `.env.example` to `.env` and configure:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cp .env.example .env
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Available settings:
|
|
84
|
+
- `ETH_RPC_URL`: Ethereum RPC endpoint (default: https://eth.llamarpc.com)
|
|
85
|
+
- `BEACON_API_URL`: Beacon chain API (default: https://beaconcha.in/api/v1)
|
|
86
|
+
- `BEACON_API_KEY`: Optional API key for beaconcha.in (higher rate limits)
|
|
87
|
+
- `ETHERSCAN_API_KEY`: Optional API key for Etherscan (recommended for accurate historical data)
|
|
88
|
+
- `CACHE_TTL_SECONDS`: Cache duration in seconds (default: 300)
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
### Docker Usage
|
|
93
|
+
|
|
94
|
+
The web dashboard runs automatically when you start the container. You can also use CLI commands inside the container:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Check operator rewards
|
|
98
|
+
docker compose exec csm-dashboard csm rewards 0xYourAddress
|
|
99
|
+
|
|
100
|
+
# Check by operator ID
|
|
101
|
+
docker compose exec csm-dashboard csm rewards --id 42
|
|
102
|
+
|
|
103
|
+
# Get detailed info with APY metrics
|
|
104
|
+
docker compose exec csm-dashboard csm rewards --id 42 --detailed
|
|
105
|
+
|
|
106
|
+
# JSON output
|
|
107
|
+
docker compose exec csm-dashboard csm rewards --id 42 --json
|
|
108
|
+
|
|
109
|
+
# List all operators
|
|
110
|
+
docker compose exec csm-dashboard csm list
|
|
111
|
+
|
|
112
|
+
# Monitor continuously (refresh every 60 seconds)
|
|
113
|
+
docker compose exec csm-dashboard csm watch 0xYourAddress --interval 60
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Local CLI Usage
|
|
117
|
+
|
|
118
|
+
### `csm rewards` - Check operator rewards
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
csm rewards [ADDRESS] [OPTIONS]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> **Note:** The `check` command is still available as an alias for backwards compatibility.
|
|
125
|
+
|
|
126
|
+
| Argument/Option | Short | Description |
|
|
127
|
+
|-----------------|-------|-------------|
|
|
128
|
+
| `ADDRESS` | | Ethereum address (required unless `--id` is provided) |
|
|
129
|
+
| `--id` | `-i` | Operator ID (skips address lookup, faster) |
|
|
130
|
+
| `--detailed` | `-d` | Include validator status from beacon chain and APY metrics |
|
|
131
|
+
| `--json` | `-j` | Output as JSON (same format as API) |
|
|
132
|
+
| `--rpc` | `-r` | Custom RPC URL |
|
|
133
|
+
|
|
134
|
+
**Examples:**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Check by address
|
|
138
|
+
csm rewards 0xYourAddress
|
|
139
|
+
|
|
140
|
+
# Check by operator ID (faster)
|
|
141
|
+
csm rewards --id 42
|
|
142
|
+
|
|
143
|
+
# Get detailed validator info and APY
|
|
144
|
+
csm rewards --id 42 --detailed
|
|
145
|
+
|
|
146
|
+
# JSON output for scripting
|
|
147
|
+
csm rewards --id 42 --json
|
|
148
|
+
|
|
149
|
+
# JSON with detailed info
|
|
150
|
+
csm rewards --id 42 --detailed --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `csm watch` - Continuous monitoring
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
csm watch ADDRESS [OPTIONS]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Argument/Option | Short | Description |
|
|
160
|
+
|-----------------|-------|-------------|
|
|
161
|
+
| `ADDRESS` | | Ethereum address to monitor (required) |
|
|
162
|
+
| `--interval` | `-i` | Refresh interval in seconds (default: 300) |
|
|
163
|
+
| `--rpc` | `-r` | Custom RPC URL |
|
|
164
|
+
|
|
165
|
+
**Examples:**
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Monitor with default 5-minute refresh
|
|
169
|
+
csm watch 0xYourAddress
|
|
170
|
+
|
|
171
|
+
# Monitor with 60-second refresh
|
|
172
|
+
csm watch 0xYourAddress --interval 60
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `csm list` - List all operators
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
csm list [OPTIONS]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Option | Short | Description |
|
|
182
|
+
|--------|-------|-------------|
|
|
183
|
+
| `--rpc` | `-r` | Custom RPC URL |
|
|
184
|
+
|
|
185
|
+
Lists all operator IDs that have rewards in the current merkle tree.
|
|
186
|
+
|
|
187
|
+
### `csm serve` - Start web dashboard
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
csm serve [OPTIONS]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Option | Description |
|
|
194
|
+
|--------|-------------|
|
|
195
|
+
| `--host` | Host to bind to (default: 127.0.0.1) |
|
|
196
|
+
| `--port` | Port to bind to (default: 8080) |
|
|
197
|
+
| `--reload` | Enable auto-reload for development |
|
|
198
|
+
|
|
199
|
+
**Examples:**
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Start on default port
|
|
203
|
+
csm serve
|
|
204
|
+
|
|
205
|
+
# Start on custom port
|
|
206
|
+
csm serve --port 3000
|
|
207
|
+
|
|
208
|
+
# Development mode with auto-reload
|
|
209
|
+
csm serve --reload
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Then open http://localhost:8080 in your browser.
|
|
213
|
+
|
|
214
|
+
**Docker:** The web dashboard is already running when you use `docker compose up`. Access it at http://localhost:3000
|
|
215
|
+
|
|
216
|
+
## JSON Output
|
|
217
|
+
|
|
218
|
+
The `--json` flag outputs data in the same format as the API, making it easy to integrate with scripts or other tools:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
csm rewards --id 333 --json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"operator_id": 333,
|
|
227
|
+
"manager_address": "0x6ac683C503CF210CCF88193ec7ebDe2c993f63a4",
|
|
228
|
+
"reward_address": "0x55915Cf2115c4D6e9085e94c8dAD710cabefef31",
|
|
229
|
+
"rewards": {
|
|
230
|
+
"current_bond_eth": 651.5523536856277,
|
|
231
|
+
"required_bond_eth": 650.2,
|
|
232
|
+
"excess_bond_eth": 1.3523536856277778,
|
|
233
|
+
"cumulative_rewards_shares": 8973877501313655495,
|
|
234
|
+
"cumulative_rewards_eth": 10.9642938931415,
|
|
235
|
+
"distributed_shares": 7867435720490255061,
|
|
236
|
+
"distributed_eth": 9.61244204773546,
|
|
237
|
+
"unclaimed_shares": 1106441780823400434,
|
|
238
|
+
"unclaimed_eth": 1.3518518454060409,
|
|
239
|
+
"total_claimable_eth": 2.7042055310338187
|
|
240
|
+
},
|
|
241
|
+
"validators": {
|
|
242
|
+
"total": 500,
|
|
243
|
+
"active": 500,
|
|
244
|
+
"exited": 0
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
With `--detailed`, additional fields are included:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"operator_id": 333,
|
|
254
|
+
"...": "...",
|
|
255
|
+
"validators": {
|
|
256
|
+
"total": 500,
|
|
257
|
+
"active": 500,
|
|
258
|
+
"exited": 0,
|
|
259
|
+
"by_status": {
|
|
260
|
+
"active": 100,
|
|
261
|
+
"pending": 0,
|
|
262
|
+
"exiting": 0,
|
|
263
|
+
"exited": 0,
|
|
264
|
+
"slashed": 0,
|
|
265
|
+
"unknown": 0
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"performance": {
|
|
269
|
+
"avg_effectiveness": 98.5
|
|
270
|
+
},
|
|
271
|
+
"apy": {
|
|
272
|
+
"historical_reward_apy_28d": 2.21,
|
|
273
|
+
"historical_reward_apy_ltd": 2.03,
|
|
274
|
+
"bond_apy": 2.54,
|
|
275
|
+
"net_apy_28d": 4.75,
|
|
276
|
+
"net_apy_ltd": 4.57
|
|
277
|
+
},
|
|
278
|
+
"active_since": "2025-02-16T12:00:00"
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## API Endpoints
|
|
283
|
+
|
|
284
|
+
- `GET /api/operator/{address_or_id}` - Get operator rewards data
|
|
285
|
+
- Query param: `?detailed=true` for validator status and APY
|
|
286
|
+
- `GET /api/operators` - List all operators with rewards
|
|
287
|
+
- `GET /api/health` - Health check
|
|
288
|
+
|
|
289
|
+
## Understanding APY Metrics
|
|
290
|
+
|
|
291
|
+
The dashboard shows three APY metrics when using the `--detailed` flag:
|
|
292
|
+
|
|
293
|
+
| Metric | What It Means |
|
|
294
|
+
|--------|---------------|
|
|
295
|
+
| **Reward APY** | Your earnings from CSM fee distributions, based on your validators' performance |
|
|
296
|
+
| **Bond APY** | Automatic growth of your stETH bond from protocol rebasing (same for all operators) |
|
|
297
|
+
| **NET APY** | Total return = Reward APY + Bond APY |
|
|
298
|
+
|
|
299
|
+
### How APY is Calculated
|
|
300
|
+
|
|
301
|
+
**Reward APY** is calculated from actual reward distribution data published by Lido. Every ~28 days, Lido calculates how much each operator earned and publishes a "distribution frame" to IPFS (a decentralized file storage network). The dashboard fetches all these historical frames to calculate both 28-day and lifetime APY.
|
|
302
|
+
|
|
303
|
+
- **28-Day APY**: Based on the most recent ~28 days of reward distributions
|
|
304
|
+
- **Lifetime APY**: Based on all periods where you earned rewards (excludes ramp-up periods with no rewards to avoid misleadingly low numbers)
|
|
305
|
+
|
|
306
|
+
**Bond APY** represents the stETH rebase rate—the automatic growth of your bond due to Ethereum staking rewards. This rate is set by the Lido protocol and applies equally to all operators. The dashboard shows the current 7-day average rate from Lido's API.
|
|
307
|
+
|
|
308
|
+
> **Note**: Bond APY shows the current stETH rate for both 28-Day and Lifetime columns, as historical rates aren't readily available.
|
|
309
|
+
|
|
310
|
+
### Why You Might Want an Etherscan API Key
|
|
311
|
+
|
|
312
|
+
The actual reward data lives on IPFS and is always accessible. However, to *discover* which IPFS files exist, the dashboard needs to find historical `DistributionLogUpdated` events on the blockchain. This can be done in several ways:
|
|
313
|
+
|
|
314
|
+
| Method | Description |
|
|
315
|
+
|--------|-------------|
|
|
316
|
+
| **With Etherscan API key** | Most reliable. Queries Etherscan directly for complete, up-to-date distribution history. |
|
|
317
|
+
| **Without API key** | Uses a built-in list of known distributions. Works fine but may be slightly behind if new distributions happened recently. |
|
|
318
|
+
|
|
319
|
+
**How to get one (free):**
|
|
320
|
+
1. Go to [etherscan.io/apis](https://etherscan.io/apis)
|
|
321
|
+
2. Create a free account
|
|
322
|
+
3. Generate an API key
|
|
323
|
+
4. Add to your `.env` file: `ETHERSCAN_API_KEY=your_key_here`
|
|
324
|
+
|
|
325
|
+
The free tier allows 5 calls/second, which is plenty for this dashboard.
|
|
326
|
+
|
|
327
|
+
## Data Sources
|
|
328
|
+
|
|
329
|
+
- **On-chain contracts**: CSModule, CSAccounting, CSFeeDistributor, stETH
|
|
330
|
+
- **Rewards tree**: https://github.com/lidofinance/csm-rewards (updates hourly)
|
|
331
|
+
- **Beacon chain**: beaconcha.in API (for validator status)
|
|
332
|
+
- **Lido API**: stETH APR data (for bond APY calculations)
|
|
333
|
+
- **IPFS**: Historical reward distribution logs (cached locally after first fetch)
|
|
334
|
+
|
|
335
|
+
## Contract Addresses (Mainnet)
|
|
336
|
+
|
|
337
|
+
- CSModule: `0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F`
|
|
338
|
+
- CSAccounting: `0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da`
|
|
339
|
+
- CSFeeDistributor: `0xD99CC66fEC647E68294C6477B40fC7E0F6F618D0`
|
|
340
|
+
- stETH: `0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84`
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Install dev dependencies
|
|
346
|
+
pip install -e ".[dev]"
|
|
347
|
+
|
|
348
|
+
# Run tests
|
|
349
|
+
pytest
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
MIT
|