Appearance
Syntax
The whole grammar by example: comments, let, addresses, ABI literals, the three places with { … } may go, and ranges.
A query
cql
// A comment runs to the end of the line.
/* A block comment
can span several. */
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH at latest-21600..latest every 7200 as s
| extend ethPerShare = s.getPooledEthByShares(1e18)
| where ethPerShare > 0
| project { block: $block, ethPerShare: format(ethPerShare, 18) }A query is any number of let statements, then one pipeline: a source and zero or more operators, each introduced by |. Keywords are case-insensitive; names are not. Whitespace and line breaks are free, so a long pipeline can be laid out one operator per line.
let
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; // a contract
let lido = abi ['function getPooledEthByShares(uint256) view returns (uint256)']; // an ABI
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
let one = 1e18; // a value
let queue = ethereum:0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1 with { abi: 'WithdrawalQueue' }; // with options
from stETH with { abi: lido } as s
| project { ethPerShare: s.getPooledEthByShares(one) with { block: 26_000_000 } }A let binds a name and ends with ;. It can hold an address (optionally with with { … } attached, so the name carries its options), an ABI, or any expression that does not read from a row. Names are looked up after aliases and columns, so a column called wstETH would shadow the let — CQL warns (CQL2900) when that happens. A let may not use a built-in ABI's name.
Addresses
An address is a chain, a colon and forty hex digits: ethereum:0x…, base:0x…. Write it in lowercase, or in the mixed case of its checksum — a mixed-case address with a wrong letter is refused (CQL1006) with the corrected spelling in the hint. There is no address without a chain.
Literals
| Kind | Examples |
|---|---|
| whole numbers | 1000000, 1_000_000, 1e18, 1.5e18 |
| decimals | 1.05, 0.5 |
| hex bytes | 0xff, 0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320 |
| strings | 'TokenRebased', "it's", 'a \'quoted\' word', 'é' |
| durations | 30s, 15m, 1h, 7d, 2w |
| dates and times | datetime('2026-09-01'), datetime('2026-09-01T12:00:00Z') |
| booleans and null | true, false, null |
| arrays | [a, b, c], [] where the type is known |
| records | { block: $block, modules: r.getStakingModuleIds() } |
Hex has an even number of digits and is bytes, not a number; tobigint(0xff) is the number. Strings take either quote and the escapes \\, \', \", \n, \t, \r and \uXXXX; a string cannot contain a raw line break.
ABI literals
cql
let a = abi ['function getTotalShares() view returns (uint256)',
'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)'];
let b = abi [{ "type": "function", "name": "getTotalShares", "inputs": [], "outputs": [{ "type": "uint256" }], "stateMutability": "view" }];
from ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 with { abi: a } as s
| project { totalShares: s.getTotalShares() }After abi, write a list of human-readable signatures, a JSON ABI array, a library name in quotes (abi 'lido'), or none. An event signature may carry indexed and parameter names; anonymous events are not supported.
with { … } in three places
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 with { abi: erc20 }; // on the name
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
from stETH with { caller: wstETH } as t // on the source
| project { balance: t.balanceOf(wstETH) with { block: 26_000_000 } } // on one callThe options are abi, caller, block and value. The innermost clause wins: a with on a call overrides the source's, which overrides the name's. Written after an address-valued expression rather than a call — locator with { abi: v } — the options apply to every call made through that value. with options has each option.
Ranges
| Written | Reads |
|---|---|
at latest, at finalized | one block: the newest, or the newest the chain will not reorganise |
at 26_000_000 | one block by number |
at latest-7200..latest | a block range, both ends included |
at 25_800_000..26_000_000 every 7200 | the range, at multiples of 7 200 plus the end |
at time '2026-09-01'..'2026-09-08' | a range by timestamp |
at time '2026-09-01'..'2026-09-08' every 1d | the same, at day boundaries plus the end |
every takes a number on a block range and a duration on a time range, not the other way round.
Names
A name is letters, digits and underscores, not starting with a digit. The words CQL keeps for itself are let, from, with, where, project, extend, expand, summarize, join, union, order, take, distinct, and, or, not, in, between, true, false, null, latest and finalized; a column with one of those names is written in backticks, `from`, or reached through its alias, t.from. Thirteen more — abi, as, asc, at, by, desc, events, every, index, kind, none, on, time — are keywords only in their position and are otherwise ordinary names.
Precedence, lowest first
c ? a : b · or · and · not · == != < <= > >= in between · + - · * /% · unary - · .member [index] (call) as type with { … }.
Two consequences worth knowing: not a == b means not (a == b), and a < b < c is refused — write a < b and b < c.
See also
- Types · Sources · Expressions