Ada Transfers Raw Sql
ada_transfers_raw_sqlLAST RESORT — arbitrary READ-ONLY SQL against the Cardano databases (cardano,
cardano_flow). The ada_* tools are the PRIORITY; use this ONLY when none can answer. No
query optimizer here: filter on the per-key tables and NEVER JOIN big tables (use
IN (SELECT …)); never GLOBAL IN (it reads the whole table).
Runs read-only ClickHouse SQL. SQL reference: https://clickhouse.com/docs/reference/home
This data runs on an older release of the engine: some functions and syntax in that reference
(map() and toJSONString() among them) do not exist here — when a statement is refused for an
unknown function or setting, use the forms shown in this description.
DATA MODEL: UTXO model with native tokens — EVERY table has ONE ROW PER ASSET: currency_id (native ADA
= 7029218, token_index = -1) and value UInt64 in RAW units. ALWAYS group by currency_id and
divide by dictGetFloat64('currency','divider',toUInt64(currency_id)) (1e6 for ADA; 1, i.e.
raw units, for a token without registered decimals and for an unknown id); a plain sum(value)
adds lovelace to token units and is meaningless, and sum() of large token values can overflow —
sum toFloat64(value).
TOKENS: token identity via the currency dict: 'symbol' / 'name' (a ticker or the hex asset name; '-'
for an unknown id), 'asset_id' (fingerprint asset1…), 'decimals', 'properties' (YAML text;
policy id = extract(dictGetString('currency','properties',toUInt64(currency_id)), 'policyId: ([0-9a-f]{56})')).
Tickers are not unique — match tokens by fingerprint or policy id. A dictGet in WHERE runs for
EVERY row read (no short-circuit), so filter by address / tx / currency_id first. Addresses are
plain strings (addr1… bech32 lowercase, stake1… reward accounts, Byron base58 Ae2… /
Ddz… case-sensitive); a script reward account (stake17… on explorers) is mostly recorded in
its stake1u… spelling, whose checksum differs — look such an account up with
ada_address_profile or ada_sent_from_address, which match both spellings. stake_key = the
staking credential of a base address — on enterprise
addresses (addr1v… / addr1w…) the column holds the payment credential re-encoded as
stake1u…, which is NOT a staking key. Tx id: tx_id_bin = unhex('<64hex>'), output
hex(tx_id_bin). Time = tx_time. Date-times come back as 'YYYY-MM-DD hh:mm:ss' UTC; select formatDateTime(tx_time, '%FT%TZ') for ISO-8601 (2026-09-21T15:04:18Z), the shape the other tools return.
TABLES: db cardano, partitioned by month on tx_date (add a plain tx_date bound whenever you can —
inside … OR … it prunes nothing): inputs_by_address / outputs_by_address (key address;
inputs carry output_tx_id_bin / output_index of the spent output and source Enum
input/withdrawal/stake_deregistration; outputs carry direction — only not_change / unknown on
ordinary outputs: change is NEVER labeled, so not_change includes change back to the payer),
inputs_by_tx / outputs_by_tx / transactions (fee_value, deposit_value, metadata) / mints (key
tx_id_bin — pass (tx_date, tx_id_bin) IN (…) when the date is known, otherwise every month is
probed; to reuse one lookup in several UNION ALL branches write (tx_date, tx_id_bin) IN identity((SELECT groupArray((tx_date, tx_id_bin)) FROM cardano.transactions WHERE tx_id_bin IN (…))), which runs once; toInt64(value) is negative for a burn), blocks (key block). Deposits are already rows:
a stake registration is an output to the stake1… address (direction stake_registration), other
protocol deposits an output with an empty address (deposit), a refund an input
(stake_deregistration) — per transaction ADA inputs = outputs + fee_value and, per token,
inputs + mints = outputs; do not add deposit_value on top.
db cardano_flow: tx_inputs / tx_outputs keyed (block, tx_index), not partitioned — the cheap
way to expand many transactions: (block, tx_index) IN (SELECT block, tx_index FROM cardano.inputs_by_address WHERE address = '…') (output_type commission / spend — never
change; the commission row has an empty address). This copy is INCOMPLETE: it misses some
transactions (a few hours of blocks in March 2023 among them) and most outputs of a token minted
in the same transaction — use cardano.*_by_tx for exact per-transaction totals.
address_transfers (AggregateFunction columns → -Merge; its amount columns ADD ALL ASSETS
together and its stake_key is empty — use it for counts only). Address labels: none in
this data — use labels_for_addresses(chain='cardano').
LIMITS: read-only; returns the rows of your query. 64-bit integers come back as JSON numbers, exact only up to 2^53: return a hash such as cityHash64(...) or any other 64-bit id as toString(x). A result of more than 25,000 rows is not cut off - the whole query
fails with an error - so aggregate (GROUP BY, count) or add a LIMIT of at most 25,000.
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"sql": "<sql>"
},
"name": "ada_transfers_raw_sql"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single read-only SELECT. Filter on the indexed *_by_address / *_by_tx tables or on cardano_flow.tx_inputs / tx_outputs by (block, tx_index); group amounts by currency_id; no JOINs over big tables. | |
| format | No | Shape of the returned data. "rows" (the default) returns one item per row. "columnar" returns a single item, {"columns":[...],"rows":[[value,...],...]}, naming the columns once and listing each row's values in that order; it is more compact for large results and carries the same values. | rows |