csm-dashboard 0.2.2__py3-none-any.whl → 0.3.1__py3-none-any.whl
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.2.dist-info → csm_dashboard-0.3.1.dist-info}/METADATA +90 -25
- csm_dashboard-0.3.1.dist-info/RECORD +32 -0
- src/abis/CSAccounting.json +22 -0
- src/abis/stETH.json +10 -0
- src/cli/commands.py +266 -45
- src/core/config.py +3 -0
- src/core/types.py +77 -5
- src/data/etherscan.py +60 -0
- src/data/ipfs_logs.py +42 -2
- src/data/lido_api.py +105 -0
- src/data/onchain.py +191 -0
- src/data/strikes.py +40 -7
- src/services/operator_service.py +352 -34
- src/web/app.py +10 -7
- src/web/routes.py +77 -11
- csm_dashboard-0.2.2.dist-info/RECORD +0 -32
- {csm_dashboard-0.2.2.dist-info → csm_dashboard-0.3.1.dist-info}/WHEEL +0 -0
- {csm_dashboard-0.2.2.dist-info → csm_dashboard-0.3.1.dist-info}/entry_points.txt +0 -0
src/web/routes.py
CHANGED
|
@@ -11,6 +11,8 @@ router = APIRouter()
|
|
|
11
11
|
async def get_operator(
|
|
12
12
|
identifier: str,
|
|
13
13
|
detailed: bool = Query(False, description="Include validator status from beacon chain"),
|
|
14
|
+
history: bool = Query(False, description="Include all historical distribution frames"),
|
|
15
|
+
withdrawals: bool = Query(False, description="Include withdrawal/claim history"),
|
|
14
16
|
):
|
|
15
17
|
"""
|
|
16
18
|
Get operator data by address or ID.
|
|
@@ -18,6 +20,8 @@ async def get_operator(
|
|
|
18
20
|
- If identifier is numeric, treat as operator ID
|
|
19
21
|
- If identifier starts with 0x, treat as Ethereum address
|
|
20
22
|
- Add ?detailed=true to include validator status breakdown
|
|
23
|
+
- Add ?history=true to include all historical distribution frames
|
|
24
|
+
- Add ?withdrawals=true to include withdrawal/claim history
|
|
21
25
|
"""
|
|
22
26
|
service = OperatorService()
|
|
23
27
|
|
|
@@ -26,9 +30,9 @@ async def get_operator(
|
|
|
26
30
|
operator_id = int(identifier)
|
|
27
31
|
if operator_id < 0 or operator_id > 1_000_000:
|
|
28
32
|
raise HTTPException(status_code=400, detail="Invalid operator ID")
|
|
29
|
-
rewards = await service.get_operator_by_id(operator_id, detailed)
|
|
33
|
+
rewards = await service.get_operator_by_id(operator_id, detailed or history, history, withdrawals)
|
|
30
34
|
elif identifier.startswith("0x"):
|
|
31
|
-
rewards = await service.get_operator_by_address(identifier, detailed)
|
|
35
|
+
rewards = await service.get_operator_by_address(identifier, detailed or history, history, withdrawals)
|
|
32
36
|
else:
|
|
33
37
|
raise HTTPException(status_code=400, detail="Invalid identifier format")
|
|
34
38
|
|
|
@@ -39,17 +43,19 @@ async def get_operator(
|
|
|
39
43
|
"operator_id": rewards.node_operator_id,
|
|
40
44
|
"manager_address": rewards.manager_address,
|
|
41
45
|
"reward_address": rewards.reward_address,
|
|
46
|
+
"curve_id": rewards.curve_id,
|
|
47
|
+
"operator_type": rewards.operator_type,
|
|
42
48
|
"rewards": {
|
|
43
|
-
"current_bond_eth":
|
|
44
|
-
"required_bond_eth":
|
|
45
|
-
"excess_bond_eth":
|
|
49
|
+
"current_bond_eth": str(rewards.current_bond_eth),
|
|
50
|
+
"required_bond_eth": str(rewards.required_bond_eth),
|
|
51
|
+
"excess_bond_eth": str(rewards.excess_bond_eth),
|
|
46
52
|
"cumulative_rewards_shares": rewards.cumulative_rewards_shares,
|
|
47
|
-
"cumulative_rewards_eth":
|
|
53
|
+
"cumulative_rewards_eth": str(rewards.cumulative_rewards_eth),
|
|
48
54
|
"distributed_shares": rewards.distributed_shares,
|
|
49
|
-
"distributed_eth":
|
|
55
|
+
"distributed_eth": str(rewards.distributed_eth),
|
|
50
56
|
"unclaimed_shares": rewards.unclaimed_shares,
|
|
51
|
-
"unclaimed_eth":
|
|
52
|
-
"total_claimable_eth":
|
|
57
|
+
"unclaimed_eth": str(rewards.unclaimed_eth),
|
|
58
|
+
"total_claimable_eth": str(rewards.total_claimable_eth),
|
|
53
59
|
},
|
|
54
60
|
"validators": {
|
|
55
61
|
"total": rewards.total_validators,
|
|
@@ -79,13 +85,65 @@ async def get_operator(
|
|
|
79
85
|
|
|
80
86
|
# Add APY metrics if available
|
|
81
87
|
if rewards.apy:
|
|
88
|
+
# Use actual excess bond for lifetime values (estimates for previous/current)
|
|
89
|
+
lifetime_bond = float(rewards.excess_bond_eth)
|
|
90
|
+
lifetime_net_total = (rewards.apy.lifetime_distribution_eth or 0) + lifetime_bond
|
|
82
91
|
result["apy"] = {
|
|
92
|
+
"previous_distribution_eth": rewards.apy.previous_distribution_eth,
|
|
93
|
+
"previous_distribution_apy": rewards.apy.previous_distribution_apy,
|
|
94
|
+
"previous_net_apy": rewards.apy.previous_net_apy,
|
|
95
|
+
"previous_bond_eth": rewards.apy.previous_bond_eth,
|
|
96
|
+
"previous_bond_apr": rewards.apy.previous_bond_apr,
|
|
97
|
+
"previous_net_total_eth": rewards.apy.previous_net_total_eth,
|
|
98
|
+
"current_distribution_eth": rewards.apy.current_distribution_eth,
|
|
99
|
+
"current_distribution_apy": rewards.apy.current_distribution_apy,
|
|
100
|
+
"current_bond_eth": rewards.apy.current_bond_eth,
|
|
101
|
+
"current_bond_apr": rewards.apy.current_bond_apr,
|
|
102
|
+
"current_net_total_eth": rewards.apy.current_net_total_eth,
|
|
103
|
+
"lifetime_distribution_eth": rewards.apy.lifetime_distribution_eth,
|
|
104
|
+
"lifetime_bond_eth": lifetime_bond, # Actual excess bond, not estimate
|
|
105
|
+
"lifetime_net_total_eth": lifetime_net_total, # Matches Total Claimable
|
|
106
|
+
# Accurate lifetime APY (per-frame bond calculation when available)
|
|
107
|
+
"lifetime_reward_apy": rewards.apy.lifetime_reward_apy,
|
|
108
|
+
"lifetime_bond_apy": rewards.apy.lifetime_bond_apy,
|
|
109
|
+
"lifetime_net_apy": rewards.apy.lifetime_net_apy,
|
|
110
|
+
"next_distribution_date": rewards.apy.next_distribution_date,
|
|
111
|
+
"next_distribution_est_eth": rewards.apy.next_distribution_est_eth,
|
|
83
112
|
"historical_reward_apy_28d": rewards.apy.historical_reward_apy_28d,
|
|
84
113
|
"historical_reward_apy_ltd": rewards.apy.historical_reward_apy_ltd,
|
|
85
114
|
"bond_apy": rewards.apy.bond_apy,
|
|
86
115
|
"net_apy_28d": rewards.apy.net_apy_28d,
|
|
87
116
|
"net_apy_ltd": rewards.apy.net_apy_ltd,
|
|
117
|
+
"uses_historical_apr": rewards.apy.uses_historical_apr,
|
|
88
118
|
}
|
|
119
|
+
# Add frames if available (from history=true)
|
|
120
|
+
if rewards.apy.frames:
|
|
121
|
+
result["apy"]["frames"] = [
|
|
122
|
+
{
|
|
123
|
+
"frame_number": f.frame_number,
|
|
124
|
+
"start_date": f.start_date,
|
|
125
|
+
"end_date": f.end_date,
|
|
126
|
+
"rewards_eth": f.rewards_eth,
|
|
127
|
+
"rewards_shares": f.rewards_shares,
|
|
128
|
+
"duration_days": f.duration_days,
|
|
129
|
+
"validator_count": f.validator_count,
|
|
130
|
+
"apy": f.apy,
|
|
131
|
+
}
|
|
132
|
+
for f in rewards.apy.frames
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
# Add withdrawal history if withdrawals=true (already fetched during data gathering)
|
|
136
|
+
if withdrawals and rewards.withdrawals:
|
|
137
|
+
result["withdrawals"] = [
|
|
138
|
+
{
|
|
139
|
+
"block_number": w.block_number,
|
|
140
|
+
"timestamp": w.timestamp,
|
|
141
|
+
"shares": w.shares,
|
|
142
|
+
"eth_value": w.eth_value,
|
|
143
|
+
"tx_hash": w.tx_hash,
|
|
144
|
+
}
|
|
145
|
+
for w in rewards.withdrawals
|
|
146
|
+
]
|
|
89
147
|
|
|
90
148
|
# Add active_since if available
|
|
91
149
|
if rewards.active_since:
|
|
@@ -95,7 +153,7 @@ async def get_operator(
|
|
|
95
153
|
if rewards.health:
|
|
96
154
|
result["health"] = {
|
|
97
155
|
"bond_healthy": rewards.health.bond_healthy,
|
|
98
|
-
"bond_deficit_eth":
|
|
156
|
+
"bond_deficit_eth": str(rewards.health.bond_deficit_eth),
|
|
99
157
|
"stuck_validators_count": rewards.health.stuck_validators_count,
|
|
100
158
|
"slashed_validators_count": rewards.health.slashed_validators_count,
|
|
101
159
|
"validators_at_risk_count": rewards.health.validators_at_risk_count,
|
|
@@ -105,6 +163,7 @@ async def get_operator(
|
|
|
105
163
|
"validators_near_ejection": rewards.health.strikes.validators_near_ejection,
|
|
106
164
|
"total_strikes": rewards.health.strikes.total_strikes,
|
|
107
165
|
"max_strikes": rewards.health.strikes.max_strikes,
|
|
166
|
+
"strike_threshold": rewards.health.strikes.strike_threshold,
|
|
108
167
|
},
|
|
109
168
|
"has_issues": rewards.health.has_issues,
|
|
110
169
|
}
|
|
@@ -135,18 +194,25 @@ async def get_operator_strikes(identifier: str):
|
|
|
135
194
|
else:
|
|
136
195
|
raise HTTPException(status_code=400, detail="Invalid identifier format")
|
|
137
196
|
|
|
138
|
-
|
|
197
|
+
# Get curve_id to determine strike threshold
|
|
198
|
+
curve_id = await service.onchain.get_bond_curve_id(operator_id)
|
|
199
|
+
strikes = await service.get_operator_strikes(operator_id, curve_id)
|
|
139
200
|
|
|
140
201
|
# Fetch frame dates for tooltip display
|
|
141
202
|
frame_dates = await service.get_recent_frame_dates(6)
|
|
142
203
|
|
|
204
|
+
# Get the threshold for this operator type
|
|
205
|
+
strike_threshold = strikes[0].strike_threshold if strikes else 3
|
|
206
|
+
|
|
143
207
|
return {
|
|
144
208
|
"operator_id": operator_id,
|
|
209
|
+
"strike_threshold": strike_threshold,
|
|
145
210
|
"frame_dates": frame_dates,
|
|
146
211
|
"validators": [
|
|
147
212
|
{
|
|
148
213
|
"pubkey": s.pubkey,
|
|
149
214
|
"strike_count": s.strike_count,
|
|
215
|
+
"strike_threshold": s.strike_threshold,
|
|
150
216
|
"strikes": s.strikes,
|
|
151
217
|
"at_ejection_risk": s.at_ejection_risk,
|
|
152
218
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
src/__init__.py,sha256=Bfqpjo9Q1XaV8DNqkf1sADzWg2ACpfZWSGLtahZE3iU,35
|
|
2
|
-
src/main.py,sha256=tj7C09FVBGBVyjKYwmElpX5M92xfydDm8RJ6-MSFdMk,951
|
|
3
|
-
src/abis/CSAccounting.json,sha256=FSZ4SyT9XMxPLBezcs77LCfJBKkrO9r_rCavD-uKm8A,815
|
|
4
|
-
src/abis/CSFeeDistributor.json,sha256=unLBacJcCHq4xsmB4xOPlVXcOrxGWNf6KDmC3Ld5u-c,1517
|
|
5
|
-
src/abis/CSModule.json,sha256=T6D6aInBoqVH3ZD6U6p3lrPa3t_ucA9V83IwE80kOuU,1687
|
|
6
|
-
src/abis/__init__.py,sha256=9HV2hKMGSoNAi8evsjzymTr4V4kowITNsX1-LPu6l98,20
|
|
7
|
-
src/abis/stETH.json,sha256=wZobN-RDcYbLs4byBs1e5A8ML6CgfihuXhEPTByB-H0,827
|
|
8
|
-
src/cli/__init__.py,sha256=mgHAwomqzAhOHJnlWrWtCguhGzhDWlkCvkzKsfoFsds,35
|
|
9
|
-
src/cli/commands.py,sha256=kSioEDzBf85MKEEWb_wq9DbRUyEtQYnHPJ9dxi-nPvk,23929
|
|
10
|
-
src/core/__init__.py,sha256=ZDHZojANK1ZFpn5lQROERTo97MYQFAxqA9APvs7ruEQ,57
|
|
11
|
-
src/core/config.py,sha256=UD4-LVHdeQeT6n9BtLXh2zZcXfX0-ZhrMVZx93E2BGk,1429
|
|
12
|
-
src/core/contracts.py,sha256=8Y72h5uUTaIMLWYdtu5O2Bjw1hTyIHOegaDs0W8r74g,525
|
|
13
|
-
src/core/types.py,sha256=dzTdZiOOzxwj4oh0ZLEWqb4jbVsA0TVm6Rqkod7yUMg,4551
|
|
14
|
-
src/data/__init__.py,sha256=DItA8aEp8Lbr0uFlJVppMaTtVEEznoA1hkRpH-vfHhk,57
|
|
15
|
-
src/data/beacon.py,sha256=9oaR7TO2WcP_D3jVTzcd9WE6NbF2WnqQDW_y5M8GsZ0,13511
|
|
16
|
-
src/data/cache.py,sha256=w1iv4rM-FVgFlaSNYdQA3CfqyhZo9-gqbZwKmzKY0Ps,2134
|
|
17
|
-
src/data/etherscan.py,sha256=mkd4KB-4JUvY_aVVqUH119GLS--_qivmeRKR70ivQkk,2832
|
|
18
|
-
src/data/ipfs_logs.py,sha256=IV_eTyktO3DaiTsXgU7huzo51I60OVo8ne9G_yvRdIk,9192
|
|
19
|
-
src/data/known_cids.py,sha256=onwTTNvAv4h0-3LZgLhqMlKzuNH2VhBqQGZ9fm8GyoE,1705
|
|
20
|
-
src/data/lido_api.py,sha256=WuVrqVOT7trxGgZX4Kavg7k9DyKyXIa_aSc_7XVLU2Y,1026
|
|
21
|
-
src/data/onchain.py,sha256=l9xQxKoXhmr0TKguba1L423_o9mdTLoeDhegMpa3ZgY,9710
|
|
22
|
-
src/data/rewards_tree.py,sha256=a-MO14b4COjOvy59FPd0jaf1dkgidlqCQKAFDinwRJU,1894
|
|
23
|
-
src/data/strikes.py,sha256=9iSW7Xm2W0rqySAJn5IwqCCKf-ef2XazC7tYHgz9REk,7480
|
|
24
|
-
src/services/__init__.py,sha256=MC7blFLAMazErCWuyYXvS6sO3uZm1z_RUOtnlIK0kSo,38
|
|
25
|
-
src/services/operator_service.py,sha256=Y6rXYdJzv7V3Nix99iiyC7NcDRfShdvDTcRtXbp_oTQ,13015
|
|
26
|
-
src/web/__init__.py,sha256=iI2c5xxXmzsNxIetm0P2qE3uVsT-ClsMfzn620r5YTU,40
|
|
27
|
-
src/web/app.py,sha256=qEIB05J0sKEeZkfHkJwotltsL-d2j1KTDS56cQl2_IU,32129
|
|
28
|
-
src/web/routes.py,sha256=z0cmH4mA3IxlHYQ-8FFYQrXCRVsi3PvUG_8ZWLixfwg,6204
|
|
29
|
-
csm_dashboard-0.2.2.dist-info/METADATA,sha256=jb13-vR-VJD327jLVqi9qi0mbdIvEz1ipQt1yS3h07U,10003
|
|
30
|
-
csm_dashboard-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
31
|
-
csm_dashboard-0.2.2.dist-info/entry_points.txt,sha256=P1Ul8ALIPBwDlVlXqTPuzJ64xxRpIJsYW8U73Tyjgtg,37
|
|
32
|
-
csm_dashboard-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|