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.
Files changed (42) hide show
  1. csm_dashboard-0.2.0/.dockerignore +75 -0
  2. csm_dashboard-0.2.0/.env.example +16 -0
  3. csm_dashboard-0.2.0/.github/workflows/docker-publish.yaml +48 -0
  4. csm_dashboard-0.2.0/.github/workflows/release.yaml +88 -0
  5. csm_dashboard-0.2.0/.gitignore +37 -0
  6. csm_dashboard-0.2.0/Dockerfile +55 -0
  7. csm_dashboard-0.2.0/PKG-INFO +354 -0
  8. csm_dashboard-0.2.0/README.md +334 -0
  9. csm_dashboard-0.2.0/abis/CSAccounting.json +37 -0
  10. csm_dashboard-0.2.0/abis/CSFeeDistributor.json +69 -0
  11. csm_dashboard-0.2.0/abis/CSModule.json +55 -0
  12. csm_dashboard-0.2.0/abis/stETH.json +42 -0
  13. csm_dashboard-0.2.0/docker-compose.yml +46 -0
  14. csm_dashboard-0.2.0/img/csm-dash-cli.png +0 -0
  15. csm_dashboard-0.2.0/img/csm-dash-web.png +0 -0
  16. csm_dashboard-0.2.0/img/logo.png +0 -0
  17. csm_dashboard-0.2.0/pyproject.toml +34 -0
  18. csm_dashboard-0.2.0/requirements.txt +56 -0
  19. csm_dashboard-0.2.0/src/__init__.py +1 -0
  20. csm_dashboard-0.2.0/src/cli/__init__.py +1 -0
  21. csm_dashboard-0.2.0/src/cli/commands.py +624 -0
  22. csm_dashboard-0.2.0/src/core/__init__.py +1 -0
  23. csm_dashboard-0.2.0/src/core/config.py +42 -0
  24. csm_dashboard-0.2.0/src/core/contracts.py +19 -0
  25. csm_dashboard-0.2.0/src/core/types.py +153 -0
  26. csm_dashboard-0.2.0/src/data/__init__.py +1 -0
  27. csm_dashboard-0.2.0/src/data/beacon.py +370 -0
  28. csm_dashboard-0.2.0/src/data/cache.py +67 -0
  29. csm_dashboard-0.2.0/src/data/etherscan.py +78 -0
  30. csm_dashboard-0.2.0/src/data/ipfs_logs.py +267 -0
  31. csm_dashboard-0.2.0/src/data/known_cids.py +30 -0
  32. csm_dashboard-0.2.0/src/data/lido_api.py +35 -0
  33. csm_dashboard-0.2.0/src/data/onchain.py +258 -0
  34. csm_dashboard-0.2.0/src/data/rewards_tree.py +58 -0
  35. csm_dashboard-0.2.0/src/data/strikes.py +214 -0
  36. csm_dashboard-0.2.0/src/main.py +39 -0
  37. csm_dashboard-0.2.0/src/services/__init__.py +1 -0
  38. csm_dashboard-0.2.0/src/services/operator_service.py +320 -0
  39. csm_dashboard-0.2.0/src/web/__init__.py +1 -0
  40. csm_dashboard-0.2.0/src/web/app.py +576 -0
  41. csm_dashboard-0.2.0/src/web/routes.py +161 -0
  42. 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
+ ![CLI Output](img/csm-dash-cli.png)
26
+
27
+ ![Web Dashboard](img/csm-dash-web.png)
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