Skip to main content
facts.token_balance_changes records how much each account’s balance moved, and nothing else. Every transfer produces two rows: the amount leaving the sender, written as a negative number, and the same amount arriving at the receiver, written as a positive one. The table is built from Transfers and covers the same ground.

Columns

ColumnTypeDescription
block_timestampTIMESTAMPThe time when the block containing this change was included. Partition column.
block_numberINT64Number of the block containing this change.
transaction_hashSTRINGHash of the transaction that moved the balance.
log_indexINT64Where the Transfer log that caused this change sat within the block.
transfer_idINT64The facts.token_transfers row this change came from. Both rows of a transfer share it.
unique_idINT64Row key: fingerprint of the transfer’s key plus the account.
chain_idSTRINGChain the change happened on.
token_typeSTRINGToken standard, such as erc20.
token_idSTRINGToken whose balance moved: {token_address}-{chain_id}.
token_addressSTRINGContract address of the token.
account_addressSTRINGAccount whose balance moved.
balance_change_rawBIGNUMERICHow much the balance moved, in the token’s raw units. Negative on the sending side.

Sample queries

This table is partitioned on block_timestamp and holds billions of rows. Put a bound on that column in every query. Leave it out and the query reads the whole table.
Find the largest net receivers of one token on a single day. The number the amounts are divided by is built from decimals as an exact whole number, so a change that nets to zero reads exactly zero.

Notes

Two kinds of transfer are left out entirely. Self-transfers, where an address sends to itself, move no balance at all. And a few transfers carry a value too large for BIGNUMERIC, the widest number BigQuery has, so they cannot be added up. In both cases the leaving row and the arriving row are dropped together, so a balance built from this table still balances exactly. This table holds individual changes in raw units and nothing else. Adding those raw numbers up would overflow BIGNUMERIC once a token’s supply passes roughly 5.79e20 tokens at 18 decimal places: the running total gets too big for the widest number BigQuery has. So the adding up happens on scaled-down amounts instead, inside the balance functions, one token at a time.