Appearance
Blocks and time
Every row is answered at one block, and at is how you choose which.
A chain has no clock but its blocks, so a query says where in the chain to look before it says what to look at. This page covers the four ways to say it, what CQL does with latest, how a step turns a range into a time series, and why two runs a minute apart can disagree.
The four forms of at
at latest— the newest block. A state source with noatmeans this.at 26_000_000— one block by number. Underscores are for your eyes only.at 25_800_000..26_000_000— a block range, both ends included.latest-7200counts back from the head;finalizedis the newest block the chain will not reorganise.at time '2026-09-20'..'2026-09-23'— a range by timestamp, resolved to blocks for you. A date alone means midnight UTC, and a full ISO time ('2026-09-20T12:00:00Z') works too.
The last form, with a step, is the one that reads a value once a day:
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH at time '2026-09-20'..'2026-09-23' every 1d as s
| project { day: $sampleAt, block: $block, ethPerShare: format(s.getPooledEthByShares(1e18), 18) }| day | block | ethPerShare |
|---|---|---|
| 2026-09-20T00:00:00Z | 26014998 | 1.244406447835658845 |
| 2026-09-21T00:00:00Z | 26022169 | 1.244482479264728163 |
| 2026-09-22T00:00:00Z | 26029315 | 1.24455806764908769 |
| 2026-09-23T00:00:00Z | 26036463 | 1.244633419602089156 |
Each row after the first is the last block before a midnight; the first is where the range starts, the first block at or after its midnight. The share rate has moved exactly once from each row to the next, because stETH rebases once a day.
An event source with no at reads a recent window — the last 10 000 blocks on Ethereum, larger on faster chains — and tells you which in the run's diagnostics (CQL3901).
latest is pinned when the run starts
The moment you press Run, CQL reads the head of every chain the query touches and pins it. latest means that block for the whole run, however long the run takes, and so does the end of any range written against it. This is why a query can fan out to thousands of calls and still describe one consistent moment.
It is also why two runs a minute apart return different rows: the pin moved. If you need the same rows back, ask for a block by number — see Getting the same rows twice.
A block beyond the pinned head is refused rather than waited for: at 99_000_000 on a chain at block 26 million reads CQL3001 · ethereum block 99000000 is beyond the pinned head 26042707. The one clamp is a time range whose end is in the future, which is cut back to the head and reported as CQL3902.
A range is a time series when you give it a step
A range of state rows reads the contract at every block in it, which is rarely what you want. Add every to sample:
every 7200reads at every block number divisible by 7 200 inside the range, plus the range end. The grid is absolute —every 100lands on…25_800_000, 25_800_100…whatever the range starts at — so two queries with the same step line up.every 1dreads at every day boundary inside atimerange, again plus the end, as the query above does. The units ares,m,h,dandw, soevery 1his an hour.
Leave every out of a long range and CQL chooses a step for you so the source stays under 10 000 rows, and says so (CQL3900). Long ranges and limits has the rule.
every belongs to a range of state rows. On a single block or an event source it is an error (CQL3005) — events are never sampled.
Two timestamps
A sampled row carries two times, and they answer different questions:
$timestampis the timestamp of the block that answered the row.$sampleAtis the boundary that selected the row — the exact midnight, forevery 1d, as thedaycolumn above — andnullon a row that was not sampled.
Use $sampleAt to put two series on one clock: their blocks can land seconds apart, and on two chains are different numbers, but their boundaries are identical. bin($sampleAt, 1h) is the idiom, and Compare a contract across chains shows it.
Turning a time into a block yourself
blockat(chain, t) gives the last block at or before an instant, and you can use it anywhere a block is wanted: with { block: blockat($chain, $timestamp - 1d) } reads a value from a day before each row. A time before the chain's first block gives null.
See also
- Sources — the full
fromclause. - Long ranges and limits — automatic sampling and the caps.
- Getting the same rows twice — pins, reorganisations and re-runs.