Skip to content

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) }

Open in workbench →

A moment later the grid fills:

blocktimeethPerShare
260280002026-09-21T19:34:47Z1.24455806764908769
260352002026-09-22T19:45:47Z1.244633419602089156
260424002026-09-23T19:56:23Z1.244710915700866902
260426752026-09-23T20:51:59Z1.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 }

Open in workbench →

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:

sinceuntilfirstlastdailyPctaprPct
2026-09-21T19:34:47Z2026-09-23T20:56:35Z1.244558067649087691.2447109157008669020.00597106097939007085151934534141509406432.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) }

Open in workbench →

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 ​