> ## Documentation Index
> Fetch the complete documentation index at: https://tokenterminal.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Balance changes

> Every increase and decrease in an account's balance, two rows per transfer.

`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](/docs/catalog/tokens/transfers) and covers the same ground.

## Columns

<table>
  <thead>
    <tr>
      <th width="280">Column</th>
      <th width="130">Type</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><code>block\_timestamp</code></td>
      <td><code>TIMESTAMP</code></td>
      <td>The time when the block containing this change was included. Partition column.</td>
    </tr>

    <tr>
      <td><code>block\_number</code></td>
      <td><code>INT64</code></td>
      <td>Number of the block containing this change.</td>
    </tr>

    <tr>
      <td><code>transaction\_hash</code></td>
      <td><code>STRING</code></td>
      <td>Hash of the transaction that moved the balance.</td>
    </tr>

    <tr>
      <td><code>log\_index</code></td>
      <td><code>INT64</code></td>
      <td>Where the Transfer log that caused this change sat within the block.</td>
    </tr>

    <tr>
      <td><code>transfer\_id</code></td>
      <td><code>INT64</code></td>
      <td>The <code>facts.token\_transfers</code> row this change came from. Both rows of a transfer share it.</td>
    </tr>

    <tr>
      <td><code>unique\_id</code></td>
      <td><code>INT64</code></td>
      <td>Row key: fingerprint of the transfer's key plus the account.</td>
    </tr>

    <tr>
      <td><code>chain\_id</code></td>
      <td><code>STRING</code></td>
      <td>Chain the change happened on.</td>
    </tr>

    <tr>
      <td><code>token\_type</code></td>
      <td><code>STRING</code></td>
      <td>Token standard, such as <code>erc20</code>.</td>
    </tr>

    <tr>
      <td><code>token\_id</code></td>
      <td><code>STRING</code></td>
      <td>Token whose balance moved: <code>\{token\_address}-\{chain\_id}</code>.</td>
    </tr>

    <tr>
      <td><code>token\_address</code></td>
      <td><code>STRING</code></td>
      <td>Contract address of the token.</td>
    </tr>

    <tr>
      <td><code>account\_address</code></td>
      <td><code>STRING</code></td>
      <td>Account whose balance moved.</td>
    </tr>

    <tr>
      <td><code>balance\_change\_raw</code></td>
      <td><code>BIGNUMERIC</code></td>
      <td>How much the balance moved, in the token's raw units. Negative on the sending side.</td>
    </tr>
  </tbody>
</table>

## Sample queries

<Warning>
  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.
</Warning>

<Tabs>
  <Tab title="Net receivers">
    **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.

    ```sql theme={null}
    select
        changes.account_address,
        sum(changes.balance_change_raw
            / cast(concat('1', repeat('0', tokens.decimals)) as bignumeric)) as net_flow
    from `facts.token_balance_changes` as changes
    join `dimensions.tokens` as tokens
        using (token_id)
    where changes.token_id = '0xdac17f958d2ee523a2206206994597c13d831ec7-ethereum'
      and changes.block_timestamp >= timestamp('2026-08-01')
      and changes.block_timestamp < timestamp('2026-08-02')
    group by changes.account_address
    order by net_flow desc
    limit 20
    ```
  </Tab>

  <Tab title="Both sides of a transfer">
    **Read both sides of the largest transfers in a window.** `transfer_id` joins a change back to the transfer that caused it.

    ```sql theme={null}
    with large_transfers as (
        select
            unique_id,
            transaction_hash,
            value_raw
        from `facts.token_transfers`
        where token_id = '0xdac17f958d2ee523a2206206994597c13d831ec7-ethereum'
          and block_timestamp >= timestamp('2026-08-01')
          and block_timestamp < timestamp('2026-08-02')
        order by cast(value_raw as bignumeric) desc
        limit 10
    )

    select
        changes.transaction_hash,
        changes.account_address,
        changes.balance_change_raw
    from `facts.token_balance_changes` as changes
    join large_transfers
        on large_transfers.unique_id = changes.transfer_id
    where changes.block_timestamp >= timestamp('2026-08-01')
      and changes.block_timestamp < timestamp('2026-08-02')
    order by changes.transaction_hash, changes.balance_change_raw
    ```
  </Tab>
</Tabs>

## 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](/docs/catalog/tokens/balances), one token at a time.
