Appearance
CQL
Read state and events from EVM chains with one short query.
CQL is a pipeline language: you name a contract, say which blocks you want, and then shape the rows with a handful of operators. No node to run, no ABI to hunt down first, no scripting. This page walks through one query, shows the rows it returns, and points you at the rest.
One query
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) }| 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 |
How it reads
let stETH = ethereum:0xae7a…; gives a name to a contract — here Lido's stETH, the liquid staking token. An address always carries its chain in front of it, so ethereum: and base: addresses never get mixed up, and every let ends with a semicolon.
from stETH at latest-21600..latest every 7200 as s is the source. at says which blocks: a range from 21 600 blocks before the newest block up to the newest, which is about three days on Ethereum, and every 7200 reads the contract once in every 7 200 of them — roughly once a day. as s calls the contract s for the rest of the query. Nothing says which ABI to use: CQL finds the contract's verified ABI for you, following a proxy to its implementation if it has to, and stETH is one.
| project { … } is an operator. Each | adds one step to the pipeline, and project keeps only the columns you list. $block and $timestamp are system columns every row carries; s.getPooledEthByShares(1e18) calls a function on the contract at that row's block and gives you its return value — the ETH one share is worth, the share rate — and format(…, 18) moves the decimal point eighteen places so it reads as ETH rather than wei.
The result is one row per sampled block plus the end of the range: four rows for three days. stETH is a rebasing token — the share rate steps up once a day, when Lido's oracle reports — and the column shows it: the value climbs between rows a day apart and holds still between the last two, which fall on the same day.
Five shapes
Almost every query is one of these, with stETH as above and router naming Lido's staking router:
- A value now.
from stETH as s | project { ethPerShare: format(s.getPooledEthByShares(1e18), 18) } - A value over time.
from stETH at 25_800_000..26_000_000 every 7200 as s | project { block: $block, ethPerShare: format(s.getPooledEthByShares(1e18), 18) } - A total.
from router as r | expand r.getStakingModuleIds() as moduleId | extend validators = r.getStakingModuleActiveValidatorsCount(moduleId) | summarize total = sum(validators), modules = count() - A time range.
from stETH at time '2026-09-20'..'2026-09-23' every 1d as s | project { day: $sampleAt, ethPerShare: format(s.getPooledEthByShares(1e18), 18) } - A storage read.
from stETH as s | project { kernel: s.$storage[keccak256('aragonOS.appStorage.kernel')] as address }
Where next
- Quick start runs the query above in the workbench and turns it into an average daily change and an annualised rate.
- How a query is put together explains rows, sources and operators from the writer's side.
- Recipes are the questions people arrive with, one query each, with real output.
- The Reference lists everything: syntax, types, operators, functions, options, diagnostics.