Appearance
Finding a contract's ABI
To call s.getPooledEthByShares(1e18) CQL needs the contract's ABI, and it looks in four places in a fixed order.
Most of the time you write nothing and it just works: the address is verified somewhere public and CQL fetches the ABI. This page is for the other times — a contract nobody verified, a proxy, an interface you already have on disk — and for reading what the workbench tells you about where an ABI came from.
The four places, in order
- An ABI in the query.
let lido = abi [...]declares one inline, as JSON or as a list of signatures, andwith { abi: lido }uses it. - A built-in.
erc20,erc721,erc1155,erc4626andmulticall3are always available by name:with { abi: erc20 }. - Your library. A name in quotes,
with { abi: 'lido' }, is looked up in the ABIs you have uploaded. They live under ABI library in the workbench's left rail, below Recent — Your library says how to add one. - The address itself. With no
abiat all, CQL asks the public verification services — Sourcify first, then Etherscan — for the source that was verified at that address, and takes its ABI.
The first three are yours to write; the fourth is the default. Once resolved, an ABI stays with that address, so the second query against the same contract does not wait.
cql
let lido = abi ['function getPooledEthByShares(uint256) view returns (uint256)'];
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH with { abi: lido } as s // 1: inline
| project { ethPerShare: format(s.getPooledEthByShares(1e18), 18) }cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH with { abi: erc20 } as t // 2: built-in
| project { supply: format(t.totalSupply(), 18) }cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 with { abi: 'lido' };
from stETH as s // 3: your library, attached to the name
| project { ethPerShare: format(s.getPooledEthByShares(1e18), 18) }with { abi: … } can sit on the let, on the from, or after a single call; the innermost one wins. with options has the rules.
Proxies
Many contracts are proxies: the address you hold forwards every call to an implementation somewhere else, and it is the implementation's ABI that describes what you can call. stETH is one. The ABI verified at its address is an Aragon app proxy's — five functions about upgrades, implementation, kernel, appId, proxyType and isDepositable — and none of them is getPooledEthByShares. CQL detects the common proxy patterns from the bytecode — EIP-1967, EIP-1167 minimal proxies, beacons, diamonds, Aragon app proxies — follows the chain to the implementation, and uses its ABI for your calls. For stETH that is 0x028271e30a695c0527a0c50ca30603fed004cdb0, and the query with no abi at all calls getPooledEthByShares as if it were the proxy's own.
When that happens, the Contract tab in the workbench (press the ⓘ beside any address in the grid) shows a line borrowed from naming the implementation. The run's diagnostics say the same, as CQL3904. Over a range, an upgraded proxy is decoded interval by interval, each with the implementation that was live then.
When nothing is found
Not available yet
Raw .call() calls are not available in the workbench yet. This page describes them as the language defines them; a query using them runs unchanged once they are.
If no service has verified source for the address, the query is refused at the call:
CQL3008 · error · ethereum:0x… has no ABI on ethereum; only .call('<sig>') is availableThree ways forward, in the order most people take them:
- Check again. A Check again button sits beside the message. Contracts get verified after the fact, and CQL remembers a not verified answer for a while; the button forgets it and asks the services again.
- Give the signature yourself.
.call('totalSupply()(uint256)')calls any function with no ABI at all — Read a contract nobody has verified. - Upload the ABI once. If you have the ABI, click Add ABI under ABI library in the rail, paste or drop it — the JSON, a compiler's output file, a
metadata.jsonor a saved Etherscan response — and give it a name. Open the entry and Insert putswith { abi: 'thatName' }at your cursor; every query after can say the same.
with { abi: none } turns the lookup off on purpose, which leaves only .call(); useful when a verified ABI is wrong for what you are doing.
Your library
ABI library in the rail lists the ABIs you have uploaded by name, each with how many items it holds. Add ABI opens a dialog: type a name, then paste the ABI, choose a file, or drop one onto the dialog. Before you save, the dialog says what it found — the kind of file and how many items — or why it cannot read it.
The dialog takes the ABI in whichever of the usual forms you have it: a JSON array of ABI items, of signatures such as 'function totalAssets() view returns (uint256)', or of both; the output file Foundry, Hardhat or Truffle writes for a contract; the metadata.json the Solidity compiler writes; or the answer Etherscan's getabi gives, saved as a file. Only the ABI is kept, whatever else the file carries.
A name is up to 64 letters, digits, _, . and - — lido, stETH-v2, vault_2024 — and capitals count, so Lido and lido are two names. The dialog tells you as you type when a name will not do. Adding under a name you already have replaces that ABI; the dialog says so, and its button reads Replace.
Click an entry to open it. Its functions, events and errors are listed as signatures, and Insert puts with { abi: 'lido' } at the cursor in the editor. Replace contents swaps in a newer ABI under the same name, and every query that names it reads the new one on its next run.
Rename and Delete change what a name finds. A query that still says the old name stops finding it and says CQL2008 until you change it, while the runs you have already made keep their rows. Rename refuses a name another entry already has, so it never overwrites one.
The editor learns about a change as soon as you make it: a new name completes inside with { abi: … }, and a CQL2008 under a name you just added goes away without reloading the page.
Reading a function nobody agrees on
When an ABI has two functions of the same name and arity — an overload — CQL cannot pick one from your arguments alone and says CQL2011 · Ambiguous overload. Name the exact one with .call: s.call('getPooledEthByShares(uint256)(uint256)', 1e18).
See also
withoptions —abi,caller,block,value.- Read a proxy — what you see when stETH resolves.
- Read a contract nobody has verified —
.call()and the library.