Appearance
Quick start
Run your first query in the workbench, then turn its time series into a rate, in about five minutes.
You need nothing but the workbench open in front of you. Every query on this page is one you can paste straight in; the rows shown are the ones it returned.
1. Run the starter
The editor opens with this query already in it, under a one-line comment. Press ⌘⏎ (or Ctrl+Enter), or click Run.
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH at latest-21600..latest every 7200 as s
| project { block: $block, time: $timestamp, ethPerShare: format(s.getPooledEthByShares(1e18), 18) }A moment later the grid fills:
| block | time | ethPerShare |
|---|---|---|
| 26028000 | 2026-09-21T19:34:47Z | 1.24455806764908769 |
| 26035200 | 2026-09-22T19:45:47Z | 1.244633419602089156 |
| 26042400 | 2026-09-23T19:56:23Z | 1.244710915700866902 |
| 26042675 | 2026-09-23T20:51:59Z | 1.244710915700866902 |
The contract is Lido's stETH, and s.getPooledEthByShares(1e18) is the ETH one share of it is worth — the share rate. at latest-21600..latest every 7200 reads that at one block in every 7 200 — about once a day — across the last three days, and always at the end of the range too, so four rows. The value steps up once a day, when Lido's oracle reports, which is why the last two rows, on the same day, agree.
2. Read the diagnostics as you type
Delete the semicolon at the end of the first line. A squiggle appears under the next word and the Diagnostics list below the editor reads:
CQL1016 · error · Unexpected `from`; expected `; at the end of a let statement`Put the semicolon back and the squiggle goes. CQL checks the whole query as you type — spelling, names, types, and whether the block you asked for exists — so most mistakes are caught before you press Run. Errors and diagnostics explains how to read one.
3. Turn the series into a rate
Keep the source and replace the project with a pipeline that folds the four samples into one row:
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH at latest-21600..latest every 7200 as s
| extend ethPerShare = format(s.getPooledEthByShares(1e18), 18)
| summarize first = first(ethPerShare), last = last(ethPerShare), since = first($timestamp), until = last($timestamp)
| extend days = todecimal(totalseconds(until - since)) / 86400
| project { since, until, first, last, dailyPct: (last / first - 1) / days * 100, aprPct: (last / first - 1) / days * 365 * 100 }extend adds ethPerShare to every row, as project did, but keeps the rest of the row with it. summarize then reduces all the rows to one: first(ethPerShare) and last(ethPerShare) are the values on the earliest and the latest row, and first($timestamp) and last($timestamp) are when those rows were. days is the span between them — until - since is a duration, totalseconds makes it a number, and todecimal lets you divide that by 86 400 without truncating.
The two percentages are the same growth on two clocks. last / first - 1 is how much one share grew over the span; divided by days and times 100 it is the average change per day, dailyPct; times 365 as well it is the annualised rate, aprPct. Run it and you get one row:
| since | until | first | last | dailyPct | aprPct |
|---|---|---|---|---|---|
| 2026-09-21T19:34:47Z | 2026-09-23T20:56:35Z | 1.24455806764908769 | 1.244710915700866902 | 0.0059710609793900708515193453414150940643 | 2.1794372574773758608045610496165093334695 |
until is the timestamp of the newest block when the run started, so it moves a little from run to run and the percentages with it. The share rate grows about six thousandths of a percent a day, a little over two percent a year.
4. Look at the dock
Under the editor a divider you can drag separates the query from a dock of four tabs. Results is the grid you have been reading, and pressing Run brings it forward. Drag a column heading to move the column, or its right edge to resize it — Alt+Arrow and Shift+Arrow do the same from the keyboard — and the grid keeps that layout by column name until you close the page. Diagnostics lists anything wrong with the query — a slip as you type, or the reason a run was refused — and comes forward on its own when a run is refused. Graphs is empty for now. Contract shows a contract's ABI.
Click the ⓘ beside any address in the grid and the Contract tab opens with that contract's ABI — where it came from, and whether a proxy borrowed it from its implementation. To put the stETH address in the grid, add it to the project:
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH as s
| project { contract: s.$address, ethPerShare: format(s.getPooledEthByShares(1e18), 18) }You get one row, and the ⓘ beside the address in it opens the Contract tab. stETH is a proxy — its own ABI is five functions about upgrades — so the tab shows a line borrowed from naming the implementation the calls were decoded with, 0x028271e30a695c0527a0c50ca30603fed004cdb0, and lists getPooledEthByShares among the functions that came from it.
Where next
- How a query is put together — the parts you just used, named.
- Blocks and time — what
latest, ranges andeverymean. - Recipes — a query for each of the usual questions.