Skip to content

Filter events by an indexed argument ​

Total the stETH sent into wstETH over ten thousand blocks, by sender.

cql
let stETH  = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

from stETH events Transfer at 26_030_000..26_040_000 as t
| where t.to == wstETH
| summarize total = sum(t.value) by sender = t.from
| order by total desc
| take 20
| project { sender, total: format(total, 18) }

Open in workbench →

sendertotal
ethereum:0x8F10B468b06c6FD214B65F87778827F7D113f9961651.582133137179292911
ethereum:0xa88f0329C2c4ce51ba3fc619BBf44efE7120Dd0d1375.15065959147960212
ethereum:0x666FEdd4CdD4E890A5aD20E7B60975409435a64A359.477007723158325754
ethereum:0xD2929024349d9206AAcC8B3e674E12a1a09cCcef288.573790880156071621
ethereum:0x0000000000000000000000000000000000000000236.159927738083342504
ethereum:0x53BCdCc18f3bADA2CD749a72B28D55Eb7AbB3dD2183.900093113299992575
ethereum:0x62897E744E0c71860EA74Bd88858B0F4Afbf72Dd150.0
ethereum:0x164578Ee9b0e58B4Ad275EB8a9510C7C56089fb8128.629432233855313877
ethereum:0x90f497EC9b3118498Bf49B68D20ed2da9Dd746F7108.094497002563515852
ethereum:0x9008D19f58AAbD9eD0D60971565AA8510560ab4173.42305783425483451
ethereum:0xf11B6481d55313103117f4a2734Fa25f4AEf502672.0
ethereum:0x11C907b3aeDbD863e551c37f21DD3F36b28A678444.385853490282307531
ethereum:0x082738D007001080A00099A000004f300615208526.071689015875585159
ethereum:0xE246ef33F236E3DF9D7F0b32b7ef770efb9d9CE815.000908177227244241
ethereum:0x21918cc62D59C1A6703e39411edF384c874C14b613.891131167337190638
ethereum:0x418796E9e99a428162A02B03A1Eb3525dCceC98113.487554276225678335
ethereum:0x96fB9D73d63c94C92896b65CdD38Cb96de6824Ff7.187317388407562653
ethereum:0xF4e791120f7791f42fedf61F8d77C12Efb387aA45.879092967010161623
ethereum:0x293436d4e4a15FBc6cCC400c14a01735E5FC74fd3.876274233531113581
ethereum:0x11111605ef067242653c980B8f6F1ffE50305Afe3.079000000000000001

Up to twenty rows: each sender that transferred stETH to wstETH in the range — which is what wrapping it does — and the total it sent, as stETH, largest first.

How it reads ​

Transfer(address indexed from, address indexed to, uint256 value) has two indexed arguments, and a where on one of them — t.to == wstETH — is answered by the node's own log filter rather than by fetching every transfer and discarding most. You write the same where either way; the difference is how long it takes. A condition on value, which is not indexed, is applied after the logs arrive.

summarize … by sender = t.from renames the key at the same time as grouping on it. from is a keyword, so an unqualified from would be refused; through the alias it is fine. sum is exact, so the format at the end moves the point on the whole total.

Variations ​

  • Out instead of in. where t.from == wstETH — unwrapping — grouping by receiver = t.to.
  • Either direction. where t.to == wstETH or t.from == wstETH is not pushed: only and-joined equalities on one argument each reach the node's filter, and an or across two arguments is evaluated after the fetch. Write two queries, or where t.to == wstETH and where t.from == wstETH separately.
  • Shares instead of balances. events TransferShares and sum(t.sharesValue); a rebase changes every balance but no share count, so totals in shares compare across days.

See also ​