Appearance
with options
The four options a call can carry — abi, caller, block, value — where each may be written, and which wins when they overlap.
cql
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 with { abi: erc20 };
from stETH with { caller: wstETH } as t
| project { balance: t.balanceOf(wstETH) with { block: 26_000_000, value: 0 } }The options
| Option | Takes | Does |
|---|---|---|
abi | a let name, a built-in, 'a library name', an inline abi […], or none | the ABI used to encode the call and decode its answer |
caller | an address | who the call appears to come from — the from of the call; the contract may answer differently per caller |
block | a bigint | the block the call is answered at, instead of the row's own |
value | a bigint, default 0 | the value sent with the call; only a payable function may take a non-zero one |
Nothing is ever sent to the chain; every call is a read, value and all. A call on a function that is not view — stETH's transfer, say — is answered as a simulation and noted as CQL3908.
block must be a whole number: for a time, blockat($chain, datetime('2026-09-01')). A constant outside the chain's range is refused before the run (CQL3003); a value computed per row that falls below zero gives null for that row.
A call on an address on another chain than the row's must carry a block (CQL3006), and the block should be one of the address's chain: blockat(chain(x), $timestamp) is that chain's last block at or before the row's time. $block is the row's own chain's number, which on the other chain is another moment altogether, and nothing refuses it. Compare a contract across chains does this after a join.
The three places
On the name. let x = ethereum:0x… with { abi: v }; attaches the options to the name, so every use of x — as a source, in a list, as a call target — carries them.
On the source. from x with { … } as s applies to every call made through s — through s.$address, too, and through a column that holds it. On a list, from [a, b] as s, a row of a takes a's options from its name and a row of b takes b's, and the source's own apply to both.
The options on a name and on a source are fixed before the query has any row, so they may use let names and constants, but not $block or a call (CQL2006), and no column is in scope there. For a block that follows the row, write it on the call: s.f() with { block: $block - 1 }.
On one call. s.f() with { … } applies to that call and not to calls on its result.
Written after an address-valued expression instead of a call — locator with { abi: v } in an extend — the options apply to every call made through that value, which is how a contract found in one call is read with a different ABI; that form is not available in the workbench yet. Follow a contract that names others shows both ways.
Which wins
The innermost. A with on the call overrides the source's, which overrides the name's, key by key: a source with { abi: v, caller: wstETH } and a call with { caller: nobody } give the call v and nobody. Two clauses in a row merge, the later key winning.
abi: none
Turns off ABI lookup for that contract, leaving only .call(). Use it when the verified ABI at an address is not the one you want to decode against.
Errors
CQL2006—$blockor a call in the options of a name or a source.CQL2043— an option that is not one of the four.CQL2042—withafter something that is not a call or an address.CQL2041— ablockthat is not abigint.CQL2053— a non-zerovalueon a function that is notpayable.