Skip to content

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

Open in workbench →

The options ​

OptionTakesDoes
abia let name, a built-in, 'a library name', an inline abi […], or nonethe ABI used to encode the call and decode its answer
calleran addresswho the call appears to come from — the from of the call; the contract may answer differently per caller
blocka bigintthe block the call is answered at, instead of the row's own
valuea bigint, default 0the 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 — $block or a call in the options of a name or a source.
  • CQL2043 — an option that is not one of the four.
  • CQL2042 — with after something that is not a call or an address.
  • CQL2041 — a block that is not a bigint.
  • CQL2053 — a non-zero value on a function that is not payable.

See also ​