codepythonmit
stakeyield
what staked ether earns, read from the contracts: lido's daily rebases from its own events, ten liquid staking tokens' rates now and a week ago, the withdrawal queue, the deposit contract.
head 50136bc2026-09-1523 files451 lines of python
record
- commits
- 4 on main, 2026-09-15 to 2026-09-15
- signed
- 4 good signature from 17BA…F3AB
- tests
- 11 passed 11 skipped
python 3.11.9 on my machine, 2026-09-23 - ci
- ci.yml lists python 3.10, 3.12, 3.13, 3.14.
- history
- every commit up to 50136bc, each with its signed record. the history itself stays off this site, so from here you cannot tie a file to a commit.
- source
- stakeyield.tar.gz, 16.9 KB · sha256
877ce5a02ddd…: the files at 50136bc, without README.md, which is shown below. - license
- mit (LICENSE)
readme
rendered from README.md at 50136bc. i left out the badges, which load from github and shields.io, and one link, which shows as plain text. the install lines point at github, which answers 404 for this account at the moment.
stakeyield
what staked ether earns, read from the contracts and nowhere else: lido's daily rebases from its own events, and for ten liquid staking tokens the ether one of them is worth now and a week ago, annualised. the lido withdrawal queue and the deposit contract's count next to it. public rpcs, no keys, no dashboard in between.
$ stakeyield
what staked ether earns, read from the contracts. block 25,982,706 (2026-09-15 11:56 utc), 7.0 days back to block 25,932,306
token issuer eth per token days ago apr eth behind
stETH lido 1 (rebasing) 2.26% 9.69m the last 7 rebases, 2.22% to 2.33%
wstETH lido, wrapped 1.244021 1.243482 2.25% 4.61m
rETH rocket pool 1.171783 1.171297 2.15% 371.06k
cbETH coinbase 1.139469 1.138950 2.37% 448.67k
sfrxETH frax 1.169382 1.168786 2.65% 43.22k
mETH mantle 1.098835 1.098381 2.15% 243.36k
osETH stakewise 1.079046 1.078620 2.06% 160.27k
ETHx stader 1.097244 1.096761 2.29% 84.89k
LsETH liquid collective 1.117306 1.116811 2.30% 320.38k
swETH swell 1.130828 1.130377 2.07% 12.42k
ankrETH ankr 1.239249 1.239249 0.00% 8.49k the rate did not move: nobody updated it, which is not a yield of zero
lido: 9.69m eth pooled; the newest rebase at 2026-09-14 12:00 utc paid 2.22% annualised over 24 hours; the protocol and the operators took 65 to 68 eth a day in fees
lido withdrawal queue: 14,592 steth in 147 requests waiting; request 135,705 is the newest, 135,558 the last one paid out
deposit contract: 2,555,318 deposits since it opened in 2020
apr: the change of ether per token between the two blocks, annualised simply, the way lido states its own;
every token quotes its rate in its own function (see the readme). eth behind: total supply at the rate.
numbers, not calls: none of this is a promise of the next week, and none of it says where ether should go.
nine of the ten tokens paid between 2.06% and 2.65% over that week, a spread of half a point that comes from fees (lido keeps 10%, rocket pool's operators take a commission), from how much of the ether sits idle waiting for a validator, and from mev luck. the tenth did not move at all: ankr's ratio is written by an off-chain updater, and for that week nobody wrote it. a rate that does not move is not a yield of zero, it is a number nobody updated, and the table says so.
install
pipx install git+https://github.com/alinaschanz/stakeyield
or clone it and run python -m stakeyield from the folder. python 3.10 or newer, no dependencies.
use
stakeyield # every token, seven days, lido's rebases and queue
stakeyield --days 30 # a month back instead (1 to 30)
stakeyield --tokens rETH,cbETH # just these
stakeyield --no-lido # the token table only
stakeyield --json
stakeyield --summary-append data/daily.csv --quiet # one row per token, the dataset
stakeyield --rpc https://your.archive.node # the older block needs state from a week ago
how it works
- lido. every day the accounting oracle reports and steth rebases; the token emits
TokenRebased(reportTimestamp, timeElapsed, preTotalShares, preTotalEther, postTotalShares, postTotalEther, sharesMintedAsFees). the apr of one report is lido's own definition, the change of ether per share annualised:(post / pre - 1) * 365 days / timeElapsed. the last seven are read witheth_getLogsin pages of a thousand blocks; the fee line issharesMintedAsFeesat the post-report share price.getTotalPooledEther()is the ether behind steth. the queue is the withdrawal queue'sunfinalizedStETH(),unfinalizedRequestNumber(),getLastRequestId(),getLastFinalizedRequestId(). - the other tokens do not rebase; the token gets worth more ether over time, and each says so
in its own function. the table in stakeyield/tokens.py keeps the call next
to the token: wsteth
stEthPerToken(), rethgetExchangeRate(), cbethexchangeRate(), sfrxethpricePerShare(), methmETHToETH(1e18)on mantle's staking contract, osethconvertToAssets(1e18)on stakewise's controller, ethxgetExchangeRate()on stader's manager, lsethunderlyingBalanceFromShares(1e18), swethswETHToETHRate(), ankrethratio()which is the other way round (tokens per ether) and is inverted. every rate is read twice, at the newest block and at the block seven days earlier (7,200 blocks a day), and annualised the same way as lido's:(now / then - 1) * 365 days / the seconds between the two blocks. - eth behind is the token's
totalSupply()times its rate: the ether those tokens are a claim on. for cbeth that is the wrapped part only; coinbase stakes more than it wraps. - the deposit contract's
get_deposit_count()is the number of 32-ether deposits ever made, a little-endian counter in abytesanswer. - the older rate is an
eth_callat a block a week old, which needs an endpoint that keeps state that far back: tenderly, drpc and mevblocker do, publicnode does not, and the list is tried in that order (or--rpcyours). a token whose contract does not answer is left out with a note.
reading the table
- an apr over seven days is what happened, not what is promised: consensus rewards move with the size of the validator set, execution rewards with the blocks' fees and mev, and one lucky block moves a small token's week.
- a rate is a contract's word about itself. lido's rebase comes from its oracle committee, rocket pool's from its oracle dao, coinbase's from coinbase; the table reads them, it cannot audit them.
- "eth behind" says how much ether stands behind the token, not how easily it comes back out: that is a queue (lido's is on the last line), a redemption window, or a market price on a dex.
- numbers, not calls: none of this says which token to hold. depegs, smart contract risk, slashing and who controls the keys are outside these columns.
the dataset
data/daily.csv gets one row per token every night at 00:47 utc: the rate now and seven days
earlier, the apr, the ether behind the token, and for steth the mean of the last seven rebases and
the withdrawal queue. columns are in data/README.md; a rerun for the same day
replaces the day.
exit codes and scripting
0 after a table, 2 when a token name is wrong, --days is out of range or no rpc answered.
--json carries every rebase with its numbers, so
stakeyield --json | jq '.lido.rebases[] | {report_time_utc, apr}' prints the week day by day.
see also
- lendrates: what a dollar earns on the lending markets, the same way
- builderwatch: who builds the blocks those validators propose
- the notes: alinaschanz.life, the short version on x
verify a release
every release carries the sdist and the wheel, a SHA256SUMS file, an opentimestamps proof of that
file, and a build provenance attestation made in github's own signing flow. with the files downloaded
into one folder:
sha256sum -c SHA256SUMS
gh attestation verify ./*.whl --owner alinaschanz
ots verify SHA256SUMS.ots
the commit itself is signed.
license
mit. numbers, not calls. not financial advice.
files
| path | lines | bytes |
|---|---|---|
.editorconfig# | 15 | 233 |
.github/dependabot.yml# | 8 | 237 |
.github/workflows/ci.yml# | 47 | 1,183 |
.github/workflows/daily.yml# | 30 | 972 |
.github/workflows/pypi.yml# | 30 | 1,103 |
.github/workflows/release.yml# | 43 | 1,526 |
.github/workflows/scorecard.yml# | 33 | 888 |
.gitignore# | 8 | 79 |
.pre-commit-config.yaml# | 13 | 321 |
CHANGELOG.md# | 15 | 739 |
LICENSE# | 21 | 1,069 |
README.md# | shown above | |
data/README.md# | 17 | 953 |
pyproject.toml# | 42 | 1,250 |
stakeyield/__init__.py# | 4 | 222 |
stakeyield/__main__.py# | 3 | 48 |
stakeyield/abi.py# | 62 | 2,573 |
stakeyield/cli.py# | 177 | 9,497 |
stakeyield/lido.py# | 69 | 2,606 |
stakeyield/rpc.py# | 124 | 6,014 |
stakeyield/tokens.py# | 102 | 5,443 |
tests/test_live.py# | 30 | 1,329 |
tests/test_offline.py# | 188 | 9,988 |
history
the latest 4 commits. all 4, with the signature of each: the commit list.