BigQuery public blockchain datasets
Full-history, continuously updated blockchain data in the bigquery-public-data project,
queryable with standard SQL. Tables are typically within minutes of the chain head.
Derived tables (traces, decoded events, token transfers) follow documented conventions
described in the per-dataset references; when a value surprises you, the chain (a
block-pinned RPC read) is the arbiter, not another indexer.
Dataset directory
| Dataset |
Chain |
Tables |
Read |
goog_blockchain_ethereum_mainnet_us |
Ethereum |
blocks, transactions, logs, receipts, traces, token_transfers, decoded_events, accounts_state, + 4 address-clustered projections (accounts, accounts_state_by_address, transactions_by_from_address, transactions_by_to_address) |
references/ethereum.md |
goog_blockchain_polygon_mainnet_us |
Polygon PoS |
blocks, transactions, logs, receipts, decoded_events |
references/evm-shared-schema.md + references/polygon.md |
goog_blockchain_arbitrum_one_us |
Arbitrum One |
shared EVM shape (same 5) |
references/evm-shared-schema.md + references/arbitrum.md |
goog_blockchain_optimism_mainnet_us |
OP Mainnet |
shared EVM shape (same 5) |
references/evm-shared-schema.md + references/optimism.md |
goog_blockchain_avalanche_contract_chain_us |
Avalanche C-Chain |
shared EVM shape (same 5) |
references/evm-shared-schema.md + references/avalanche.md |
goog_blockchain_cronos_mainnet_us |
Cronos |
shared EVM shape (same 5) |
references/evm-shared-schema.md + references/cronos.md |
goog_blockchain_tron_mainnet_us |
TRON |
blocks, transactions, logs, receipts, decoded_events, tron_internal_transactions |
references/tron.md |
crypto_ethereum |
Ethereum (legacy ethereum-etl layout) |
blocks, transactions, logs, token_transfers, traces, contracts, tokens, amended_tokens, balances, tokens_latest, token_supply_latest |
references/crypto-ethereum.md |
crypto_bitcoin |
Bitcoin |
blocks, transactions, inputs, outputs |
references/crypto-bitcoin.md |
Always fully qualify tables with backticks: `bigquery-public-data.<dataset>.<table>`.
Two Ethereum datasets exist on purpose. goog_blockchain_ethereum_mainnet_us is the
native data model (richer: receipts, decoded events, per-block account states with
proofs, lossless value columns). crypto_ethereum is the older ethereum-etl layout
(receipt fields embedded in transactions, plus tokens/balances/contracts derived
tables) — reach for it when a query or downstream tool was written for that layout, or
when you need token metadata or contract creation data. For ETH balances at a
block use the native dataset's accounts_state_by_address;
crypto_ethereum.balances is a snapshot of current balances only, with no history.
Column names differ between the two (block_number vs number,
block_timestamp vs timestamp); they are not interchangeable.
Workflow
- Pick the dataset from the directory above and read its reference file(s) before
writing SQL — they carry the schemas and the per-chain gotchas that make naive
queries silently wrong.
- Draft the query selecting only the columns you need, with a block-range or
time-range constraint.
- Dry-run it and check the bytes scanned (see
references/query-execution.md for how, with any
execution setup;
scripts/bq-safe.sh does dry-run → cap → run in one call). If
the estimate is far above what the row selectivity suggests, your filter is not
pruning — fix the filter, don't just run it.
- Run under a hard byte cap (
--maximum_bytes_billed, or the client's
equivalent) sized to the estimate, with --location=US, then sanity-check
magnitudes (wei vs ETH, sun vs TRX, satoshi vs BTC).
To locate a column, table or convention without opening files, grep the references:
grep -rn 'token_transfers' references/.
Start from a worked query when one fits:
references/recipes.md holds executed recipes with measured
bytes (yearly block anchors for every chain, date→block lookup, daily counts and
active addresses, fees, token transfers, NFT mints, address history, balance at a
block, TRON USDT with base58 addresses, Bitcoin coinbase) — copy the closest one and change the
window.
If no query execution path is available in the current environment, write the final
SQL, state the dry-run expectation, and hand it to the user to paste into
https://console.cloud.google.com/bigquery — never fabricate results.
What a finished answer contains
An on-chain number is only usable with the definition attached, so report:
- The answer, in the unit the user asked for (ETH, not wei; a date, not a block).
- The window, pinned: the block range or timestamp bounds actually queried, so
the number can be reproduced tomorrow.
- What it excludes, when a convention changes the meaning: system or deposit
transactions, internal-call transfers, empty blocks, an ERC-20-only filter. One
clause is enough, and it is what separates a right number from a misleading one.
- What it cost: bytes billed (not the estimate) and, on-demand, the dollar
figure at the rate you used.
- The SQL, so the user can re-run or adapt it.
If a result contradicts a well-known public figure, say so and name the likely
reason (a different definition, a different window) rather than quietly shipping it.
Cost control (bytes scanned is everything)
BigQuery compute has two pricing regimes, and which one applies changes what a
"cost" is:
- On-demand (the default for most projects): you pay per TiB for the
columns you reference over the partitions you touch, after a monthly free
allowance per billing account. A dry run's bytes ARE the price. The list rate,
the allowance and the per-query minimum change — the figures this skill uses
are dated in references/query-execution.md
§ Pricing facts; quote them as "at the time of writing" and verify against the
project's real
totalBytesBilled when it matters.
- Capacity (BigQuery Editions / reservations): the project is attached to a
slot reservation and pays for slot-time, not bytes. Dry-run bytes then
estimate the work, not a dollar price — per-query dollar figures like the
table below don't apply, but every bytes-reduction rule below still cuts slot
usage and wall-clock the same way. To check which regime a project is on, see
references/query-execution.md.
The dollar figures in this skill assume on-demand at the US list rate in force
when the sizes were measured (stamped in each reference); bytes are the durable
number. The big tables here are multi-terabyte either way:
| Largest tables |
Full scan |
SELECT * cost |
goog_blockchain_polygon_mainnet_us.logs |
32.7 TB |
~$205 |
goog_blockchain_polygon_mainnet_us.decoded_events |
30.3 TB |
~$189 |
goog_blockchain_ethereum_mainnet_us.accounts_state |
53.5 TB |
~$334 |
goog_blockchain_ethereum_mainnet_us.traces |
12.8 TB |
~$80 |
crypto_ethereum.traces |
13.5 TB |
~$85 |
goog_blockchain_tron_mainnet_us.transactions |
8.1 TB |
~$51 |
Every reference file carries the full per-table size list. Rules:
- Cap every real run. A dry run is advice;
maximum_bytes_billed is the one
control that makes an over-budget job fail instead of bill. Size the cap to
the estimate (a 1 GB cap on a 50 MB estimate costs nothing), and pass
--location=US — the datasets are US-only and a project defaulting to another
region fails with "dataset not found in location".
- Never
SELECT * on anything but the metadata-sized tables (tokens,
amended_tokens). Wide STRING columns (input, logs_bloom, data, code,
bytecode, account_proof) dominate table width — not selecting them is the
single biggest saving.
LIMIT does not reduce cost. It caps returned rows, not scanned bytes.
- Constrain the block range on every query — and constrain both the block
number and the timestamp column where the table has both. Physical partitioning
differs by dataset and can change as the datasets evolve (measured:
goog_blockchain_* tables prune on block_number ranges; crypto_ethereum
prunes on the day of timestamp/block_timestamp; crypto_bitcoin prunes on
block_timestamp_month / timestamp_month). Supplying both predicates costs at
most the second column's bytes and buys reproducibility plus protection against
a layout change; the dry run tells you what actually pruned. On tables without a
block_number column (transactions/receipts on the shared EVM shape and
TRON), bound block_timestamp — an unbounded join side can scan the whole
table.
- Tables clustered by address-like columns (the Ethereum
accounts /
*_by_address projections) make address point lookups cheap: billed bytes are
a small fraction of the table. Whether the dry-run estimate reflects the
clustering depends on the object's layout — when a cluster-key point lookup
estimates like a full scan, the estimate is pessimistic; billed bytes are the
number to trust either way. The same filter on the base transactions table
reads the whole column.
- A dry run is free. When in doubt, dry-run twice (with and without a filter) and
compare.
Correctness rules that apply everywhere
- Numeric precision. Wei/sun-denominated values can exceed every fixed-width SQL
type. The datasets handle this three ways — check the table's schema:
BIGNUMERIC columns hold up to ~128-bit values; paired *_lossless STRING columns
hold the exact 256-bit decimal; dual-representation RECORD columns
({string_value, bignumeric_value}) hold both, and bignumeric_value is NULL when
the value overflows while string_value never is. Never round-trip money values
through FLOAT64; sum in BIGNUMERIC, or parse the string/lossless column when exact
256-bit math matters.
- Hex conventions. Hashes, addresses and byte payloads are lowercase,
0x-prefixed
STRING. Compare with lowercase literals. Check the address before you lowercase it:
the mixed-case (EIP-55) form is a checksum that catches a mistyped digit, and
lowercasing throws that check away. A point lookup on a clustered address column
returning zero rows means the account is absent from the dataset — far more often a
wrong address than missing data.
- Timestamps are UTC
TIMESTAMP columns.
- Units. EVM chains: wei (1 ETH = 1e18 wei), gas in gas units. TRON: sun
(1 TRX = 1e6 sun), "gas" columns denominated in Energy. Bitcoin: satoshi
(1 BTC = 1e8 satoshi).
- Do not deduplicate by transaction hash alone. On Cronos, Arbitrum Classic and
pre-Bedrock Optimism the same hash can legitimately appear in more than one block
(details in the chain references). The safe row identity is
(block_number-or-hash, transaction_hash).
- Head-adjacent queries move. The tables track the chain head; two queries near
the head are not repeatable. Pin an upper block bound for anything that must be
reproducible.
- The chain is the ground truth: when a value here surprises you, the right
arbitration is a block-pinned RPC read (
eth_getBalance, eth_getProof,
trace_block, …), not another indexer — third-party indexers apply
their own conventions to derived data.
Query execution
Read references/query-execution.md the first time you
need to actually run (or price) a query in a given environment — it covers the bq
CLI, BigQuery MCP tools, pay-per-query proxies, and the no-execution fallback. Its
last section maps the errors these datasets actually produce to their fixes; check
it before debugging a failed run from first principles.
Bundled scripts (each needs the bq CLI and python3, both on PATH; none bills
anything unless you pass --run). Every script takes --help:
| Script |
Does |
Cost |
scripts/bq-safe.sh 'SQL' |
dry run, print bytes + $ estimate; --run --cap 100MB executes under a hard cap |
free / capped |
scripts/check-partitioning.sh [dataset] |
print partitioning + clustering of every table (bq show) |
free |
scripts/regenerate-sizes.sh <dataset> |
rebuild a reference's size table from dry runs |
free |
scripts/regenerate-schema-tables.py <dataset> |
rebuild schema tables from INFORMATION_SCHEMA |
two 10 MB-minimum metadata queries |
1---2name: bigquery-blockchain-datasets3description: Query the BigQuery public blockchain datasets — Ethereum, Polygon, Arbitrum, Optimism, Avalanche, Cronos and TRON (goog_blockchain_*_us), plus crypto_ethereum and crypto_bitcoin — with correct SQL at controlled cost. Use before writing any SQL against bigquery-public-data blockchain tables, when the user names one of these datasets, or for on-chain analytics on these chains: blocks, transactions, logs and events, traces and internal transactions, token transfers and holders, wallet history, gas fees, ETH balances, contracts, NFT activity, USDT on TRON, Bitcoin UTXOs. Not for private BigQuery datasets, Dune/Flipside SQL, or other bigquery-public-data crypto_* datasets (Solana, Litecoin, …).4license: MIT5---67# BigQuery public blockchain datasets89Full-history, continuously updated blockchain data in the `bigquery-public-data` project,10queryable with standard SQL. Tables are typically within minutes of the chain head.11Derived tables (traces, decoded events, token transfers) follow documented conventions12described in the per-dataset references; when a value surprises you, the chain (a13block-pinned RPC read) is the arbiter, not another indexer.1415## Dataset directory1617| Dataset | Chain | Tables | Read |18|---|---|---|---|19| `goog_blockchain_ethereum_mainnet_us` | Ethereum | blocks, transactions, logs, receipts, traces, token_transfers, decoded_events, accounts_state, + 4 address-clustered projections (`accounts`, `accounts_state_by_address`, `transactions_by_from_address`, `transactions_by_to_address`) | [references/ethereum.md](references/ethereum.md) |20| `goog_blockchain_polygon_mainnet_us` | Polygon PoS | blocks, transactions, logs, receipts, decoded_events | [references/evm-shared-schema.md](references/evm-shared-schema.md) + [references/polygon.md](references/polygon.md) |21| `goog_blockchain_arbitrum_one_us` | Arbitrum One | shared EVM shape (same 5) | [references/evm-shared-schema.md](references/evm-shared-schema.md) + [references/arbitrum.md](references/arbitrum.md) |22| `goog_blockchain_optimism_mainnet_us` | OP Mainnet | shared EVM shape (same 5) | [references/evm-shared-schema.md](references/evm-shared-schema.md) + [references/optimism.md](references/optimism.md) |23| `goog_blockchain_avalanche_contract_chain_us` | Avalanche C-Chain | shared EVM shape (same 5) | [references/evm-shared-schema.md](references/evm-shared-schema.md) + [references/avalanche.md](references/avalanche.md) |24| `goog_blockchain_cronos_mainnet_us` | Cronos | shared EVM shape (same 5) | [references/evm-shared-schema.md](references/evm-shared-schema.md) + [references/cronos.md](references/cronos.md) |25| `goog_blockchain_tron_mainnet_us` | TRON | blocks, transactions, logs, receipts, decoded_events, tron_internal_transactions | [references/tron.md](references/tron.md) |26| `crypto_ethereum` | Ethereum (legacy ethereum-etl layout) | blocks, transactions, logs, token_transfers, traces, contracts, tokens, amended_tokens, balances, tokens_latest, token_supply_latest | [references/crypto-ethereum.md](references/crypto-ethereum.md) |27| `crypto_bitcoin` | Bitcoin | blocks, transactions, inputs, outputs | [references/crypto-bitcoin.md](references/crypto-bitcoin.md) |2829Always fully qualify tables with backticks: `` `bigquery-public-data.<dataset>.<table>` ``.3031Two Ethereum datasets exist on purpose. `goog_blockchain_ethereum_mainnet_us` is the32native data model (richer: receipts, decoded events, per-block account states with33proofs, lossless value columns). `crypto_ethereum` is the older ethereum-etl layout34(receipt fields embedded in `transactions`, plus tokens/balances/contracts derived35tables) — reach for it when a query or downstream tool was written for that layout, or36when you need token metadata or contract creation data. For **ETH balances at a37block** use the native dataset's `accounts_state_by_address`;38`crypto_ethereum.balances` is a snapshot of current balances only, with no history.39Column names differ between the two (`block_number` vs `number`,40`block_timestamp` vs `timestamp`); they are not interchangeable.4142## Workflow43441. Pick the dataset from the directory above and **read its reference file(s)** before45 writing SQL — they carry the schemas and the per-chain gotchas that make naive46 queries silently wrong.472. Draft the query selecting **only the columns you need**, with a block-range or48 time-range constraint.493. **Dry-run it** and check the bytes scanned (see50 [references/query-execution.md](references/query-execution.md) for how, with any51 execution setup; `scripts/bq-safe.sh` does dry-run → cap → run in one call). If52 the estimate is far above what the row selectivity suggests, your filter is not53 pruning — fix the filter, don't just run it.544. **Run under a hard byte cap** (`--maximum_bytes_billed`, or the client's55 equivalent) sized to the estimate, with `--location=US`, then sanity-check56 magnitudes (wei vs ETH, sun vs TRX, satoshi vs BTC).5758To locate a column, table or convention without opening files, grep the references:59`grep -rn 'token_transfers' references/`.6061Start from a worked query when one fits:62[references/recipes.md](references/recipes.md) holds executed recipes with measured63bytes (yearly block anchors for every chain, date→block lookup, daily counts and64active addresses, fees, token transfers, NFT mints, address history, balance at a65block, TRON USDT with base58 addresses, Bitcoin coinbase) — copy the closest one and change the66window.6768If no query execution path is available in the current environment, write the final69SQL, state the dry-run expectation, and hand it to the user to paste into70`https://console.cloud.google.com/bigquery` — never fabricate results.7172### What a finished answer contains7374An on-chain number is only usable with the definition attached, so report:7576- **The answer**, in the unit the user asked for (ETH, not wei; a date, not a block).77- **The window**, pinned: the block range or timestamp bounds actually queried, so78 the number can be reproduced tomorrow.79- **What it excludes**, when a convention changes the meaning: system or deposit80 transactions, internal-call transfers, empty blocks, an ERC-20-only filter. One81 clause is enough, and it is what separates a right number from a misleading one.82- **What it cost**: bytes billed (not the estimate) and, on-demand, the dollar83 figure at the rate you used.84- **The SQL**, so the user can re-run or adapt it.8586If a result contradicts a well-known public figure, say so and name the likely87reason (a different definition, a different window) rather than quietly shipping it.8889## Cost control (bytes scanned is everything)9091BigQuery compute has two pricing regimes, and which one applies changes what a92"cost" is:9394- **On-demand** (the default for most projects): you pay per TiB for the95 **columns you reference over the partitions you touch**, after a monthly free96 allowance per billing account. A dry run's bytes ARE the price. The list rate,97 the allowance and the per-query minimum change — the figures this skill uses98 are dated in [references/query-execution.md](references/query-execution.md)99 § Pricing facts; quote them as "at the time of writing" and verify against the100 project's real `totalBytesBilled` when it matters.101- **Capacity (BigQuery Editions / reservations)**: the project is attached to a102 slot reservation and pays for **slot-time**, not bytes. Dry-run bytes then103 estimate the *work*, not a dollar price — per-query dollar figures like the104 table below don't apply, but every bytes-reduction rule below still cuts slot105 usage and wall-clock the same way. To check which regime a project is on, see106 [references/query-execution.md](references/query-execution.md).107108The dollar figures in this skill assume on-demand at the US list rate in force109when the sizes were measured (stamped in each reference); bytes are the durable110number. The big tables here are multi-terabyte either way:111112| Largest tables | Full scan | `SELECT *` cost |113|---|---|---|114| `goog_blockchain_polygon_mainnet_us.logs` | 32.7 TB | ~$205 |115| `goog_blockchain_polygon_mainnet_us.decoded_events` | 30.3 TB | ~$189 |116| `goog_blockchain_ethereum_mainnet_us.accounts_state` | 53.5 TB | ~$334 |117| `goog_blockchain_ethereum_mainnet_us.traces` | 12.8 TB | ~$80 |118| `crypto_ethereum.traces` | 13.5 TB | ~$85 |119| `goog_blockchain_tron_mainnet_us.transactions` | 8.1 TB | ~$51 |120121Every reference file carries the full per-table size list. Rules:122123- **Cap every real run.** A dry run is advice; `maximum_bytes_billed` is the one124 control that makes an over-budget job *fail* instead of bill. Size the cap to125 the estimate (a 1 GB cap on a 50 MB estimate costs nothing), and pass126 `--location=US` — the datasets are US-only and a project defaulting to another127 region fails with "dataset not found in location".128- **Never `SELECT *`** on anything but the metadata-sized tables (`tokens`,129 `amended_tokens`). Wide STRING columns (`input`, `logs_bloom`, `data`, `code`,130 `bytecode`, `account_proof`) dominate table width — not selecting them is the131 single biggest saving.132- **`LIMIT` does not reduce cost.** It caps returned rows, not scanned bytes.133- **Constrain the block range on every query** — and constrain **both** the block134 number and the timestamp column where the table has both. Physical partitioning135 differs by dataset and can change as the datasets evolve (measured:136 `goog_blockchain_*` tables prune on `block_number` ranges; `crypto_ethereum`137 prunes on the day of `timestamp`/`block_timestamp`; `crypto_bitcoin` prunes on138 `block_timestamp_month` / `timestamp_month`). Supplying both predicates costs at139 most the second column's bytes and buys reproducibility plus protection against140 a layout change; the dry run tells you what actually pruned. On tables without a141 `block_number` column (`transactions`/`receipts` on the shared EVM shape and142 TRON), bound `block_timestamp` — an unbounded join side can scan the whole143 table.144- Tables clustered by address-like columns (the Ethereum `accounts` /145 `*_by_address` projections) make address point lookups cheap: billed bytes are146 a small fraction of the table. Whether the dry-run **estimate** reflects the147 clustering depends on the object's layout — when a cluster-key point lookup148 estimates like a full scan, the estimate is pessimistic; billed bytes are the149 number to trust either way. The same filter on the base `transactions` table150 reads the whole column.151- A dry run is free. When in doubt, dry-run twice (with and without a filter) and152 compare.153154## Correctness rules that apply everywhere155156- **Numeric precision.** Wei/sun-denominated values can exceed every fixed-width SQL157 type. The datasets handle this three ways — check the table's schema:158 `BIGNUMERIC` columns hold up to ~128-bit values; paired `*_lossless` STRING columns159 hold the exact 256-bit decimal; dual-representation RECORD columns160 (`{string_value, bignumeric_value}`) hold both, and `bignumeric_value` is NULL when161 the value overflows while `string_value` never is. **Never round-trip money values162 through FLOAT64**; sum in BIGNUMERIC, or parse the string/lossless column when exact163 256-bit math matters.164- **Hex conventions.** Hashes, addresses and byte payloads are lowercase, `0x`-prefixed165 STRING. Compare with lowercase literals. Check the address before you lowercase it:166 the mixed-case (EIP-55) form is a checksum that catches a mistyped digit, and167 lowercasing throws that check away. A point lookup on a clustered address column168 returning zero rows means the account is absent from the dataset — far more often a169 wrong address than missing data.170- **Timestamps** are UTC `TIMESTAMP` columns.171- **Units.** EVM chains: wei (1 ETH = 1e18 wei), gas in gas units. TRON: sun172 (1 TRX = 1e6 sun), "gas" columns denominated in Energy. Bitcoin: satoshi173 (1 BTC = 1e8 satoshi).174- **Do not deduplicate by transaction hash alone.** On Cronos, Arbitrum Classic and175 pre-Bedrock Optimism the same hash can legitimately appear in more than one block176 (details in the chain references). The safe row identity is177 `(block_number-or-hash, transaction_hash)`.178- **Head-adjacent queries move.** The tables track the chain head; two queries near179 the head are not repeatable. Pin an upper block bound for anything that must be180 reproducible.181- The chain is the ground truth: when a value here surprises you, the right182 arbitration is a block-pinned RPC read (`eth_getBalance`, `eth_getProof`,183 `trace_block`, …), not another indexer — third-party indexers apply184 their own conventions to derived data.185186## Query execution187188Read [references/query-execution.md](references/query-execution.md) the first time you189need to actually run (or price) a query in a given environment — it covers the `bq`190CLI, BigQuery MCP tools, pay-per-query proxies, and the no-execution fallback. Its191last section maps the errors these datasets actually produce to their fixes; check192it before debugging a failed run from first principles.193194Bundled scripts (each needs the `bq` CLI and `python3`, both on `PATH`; none bills195anything unless you pass `--run`). Every script takes `--help`:196197| Script | Does | Cost |198|---|---|---|199| `scripts/bq-safe.sh 'SQL'` | dry run, print bytes + $ estimate; `--run --cap 100MB` executes under a hard cap | free / capped |200| `scripts/check-partitioning.sh [dataset]` | print partitioning + clustering of every table (`bq show`) | free |201| `scripts/regenerate-sizes.sh <dataset>` | rebuild a reference's size table from dry runs | free |202| `scripts/regenerate-schema-tables.py <dataset>` | rebuild schema tables from `INFORMATION_SCHEMA` | two 10 MB-minimum metadata queries |