> ## 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.

> ## Agent Instructions
> To query the Token Terminal data catalog, read https://tokenterminal.com/docs/catalog/agents-manual.md first. It is the whole catalog as one page: table naming grammar, key columns, partition and cluster rules, units, additivity, and the tables that are documented but not served yet.
> Never query a catalog table on a time bound alone. Also filter its cluster key, which you read from INFORMATION_SCHEMA.COLUMNS; an empty result means the object is a view, whose pruning contract is on its page. Compute is billed to the caller's own Google Cloud project.

# Balances

> Holder balances on any past date, computed at query time.

Holder balances are computed at query time from [Transfers](https://tokenterminal.com/docs/catalog/tokens/transfers.md): every movement splits into its debit and its credit leg, and the legs add up per account. Each function takes a chain, a token address and a token standard, and reads that token's whole movement history.

**There are two pairs, and which one you want depends on the token.** The generic pair below works on any token on any chain the catalog conforms, from transfer records alone. The curated pair works only on the tokenized assets in the registry, and for those it is the one to use: its numbers are reconciled against the supply the mint and burn events account for, and it refuses to answer where the answer would be wrong rather than returning a figure nobody checked.

> **Any token**
>
> `calculate_latest_token_balances` and `calculate_historical_eod_token_balances`, documented below. Broad coverage, no per-issuer handling. On a rebasing token these drift, because a balance that grows without a transfer leaves no record to add up.

> **[Tokenized assets](https://tokenterminal.com/docs/catalog/assets/balance-changes.md)**
>
> `calculate_latest_asset_token_balances` and `calculate_historical_eod_asset_token_balances`. Registry assets only, and they return a refusal rather than a number where a deployment cannot be modelled. **In progress**: under test, not yet served.

## Functions

**Latest**

`functions.calculate_latest_token_balances` returns the current holders of one token: each account's most recent end-of-day balance, kept where it is positive. Picking the most recent row first and applying the positive filter second is what makes the result the holder set today.

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

  <tbody>
    <tr>
      <td>`p_chain_id`</td>
      <td>`STRING`</td>
      <td>Chain the token is deployed on, for example `ethereum`.</td>
    </tr>

    <tr>
      <td>`p_token_address`</td>
      <td>`STRING`</td>
      <td>Contract address of the token. An `erc20` address is lowercased for you, so a checksummed address copied from an explorer resolves; every other standard is matched as the chain writes it, `spl` and `trc20` being base58.</td>
    </tr>

    <tr>
      <td>`p_token_type`</td>
      <td>`STRING`</td>
      <td>Token standard: `erc20`, `spl` or `trc20`.</td>
    </tr>
  </tbody>
</table>

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

  <tbody>
    <tr>
      <td>`account_address`</td>
      <td>`STRING`</td>
      <td>Account holding a positive balance: a wallet on the log-shaped chains, an SPL token account on Solana.</td>
    </tr>

    <tr>
      <td>`balance`</td>
      <td>`BIGNUMERIC`</td>
      <td>Latest end-of-day balance in token units. Added up in exact whole-number arithmetic and scaled by the token's `decimals`, so a drained account reads exactly zero and leaves the holder set.</td>
    </tr>
  </tbody>
</table>

**Historical**

`functions.calculate_historical_eod_token_balances` returns each holder's end-of-day balance, one row per day the balance moved. A balance on any date is the most recent row at or before that date, so an account that moved tokens on Monday and again on Friday reads its Monday balance until Friday's row replaces it.

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

  <tbody>
    <tr>
      <td>`p_chain_id`</td>
      <td>`STRING`</td>
      <td>Chain the token is deployed on, for example `ethereum`.</td>
    </tr>

    <tr>
      <td>`p_token_address`</td>
      <td>`STRING`</td>
      <td>Contract address of the token. An `erc20` address is lowercased for you, so a checksummed address copied from an explorer resolves; every other standard is matched as the chain writes it, `spl` and `trc20` being base58.</td>
    </tr>

    <tr>
      <td>`p_token_type`</td>
      <td>`STRING`</td>
      <td>Token standard: `erc20`, `spl` or `trc20`.</td>
    </tr>
  </tbody>
</table>

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

  <tbody>
    <tr>
      <td>`balance_date`</td>
      <td>`DATE`</td>
      <td>Day the balance moved.</td>
    </tr>

    <tr>
      <td>`account_address`</td>
      <td>`STRING`</td>
      <td>Account holding the balance: a wallet on the log-shaped chains, an SPL token account on Solana.</td>
    </tr>

    <tr>
      <td>`balance`</td>
      <td>`BIGNUMERIC`</td>
      <td>End-of-day balance in token units. Added up in exact whole-number arithmetic and scaled by the token's `decimals`, so a drained account reads exactly zero.</td>
    </tr>
  </tbody>
</table>

**Curated (tokenized assets)**

> [!NOTE]
>
> **In progress.** The curated pair is under test and not yet served. [Contact us](mailto:sales@tokenterminal.xyz) if you want early access.

`functions.calculate_latest_asset_token_balances` and `functions.calculate_historical_eod_asset_token_balances` take the same three parameters and cover the [tokenized asset registry](https://tokenterminal.com/docs/catalog/assets/registry.md) only. They add two columns to the ones above, because a curated answer states whether it is trustworthy.

**A refusal is a row, not an empty result.** Where a deployment is unmodelled, fails to reconcile, or falls outside the curated registry, the function returns exactly one row with `served` false, a reason, and `NULL` in every numeric column. An empty result would be indistinguishable from a token nobody holds or from a mistyped address, and an aggregate over a refusal reads `NULL` rather than a plausible wrong total.

Check `served` before using a number. A caller who ignores it and sums `balance` gets `NULL`. That is the right answer to an unanswerable question.

`account_owner` below resolves the Solana token accounts that otherwise count one wallet as several holders.

<table>
  <thead>
    <tr>
      <th width="240">Return column</th>
      <th width="130">Type</th>
      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>`served`</td>
      <td>`BOOL`</td>
      <td>Whether the numbers in this row mean anything. False on the single refusal row.</td>
    </tr>

    <tr>
      <td>`refusal_reason`</td>
      <td>`STRING`</td>
      <td>Why, in plain language, when `served` is false. Names the generic function where the token simply is not a curated asset.</td>
    </tr>

    <tr>
      <td>`account_owner`</td>
      <td>`STRING`</td>
      <td>The holder, where the chain distinguishes a holding account from its owner as Solana does. **Group by this for a cap table**, or one wallet's several token accounts each count as a holder.</td>
    </tr>
  </tbody>
</table>

## Sample queries

**Current holders**

Each call adds up the token's whole movement history, so a one-off holder set costs a full scan. [Assets ▸ Metrics](https://tokenterminal.com/docs/catalog/assets/metrics.md) holds a daily supply and holder-count series for curated assets.

```sql theme={null}
select
    account_address,
    balance
from `functions.calculate_latest_token_balances`(
    'ethereum',
    '0xdac17f958d2ee523a2206206994597c13d831ec7',
    'erc20'
)
order by balance desc
limit 100
```

**Holders on a date**

The most recent row at or before the date is the balance on that date, so `row_number()` picks it per account. Accounts at zero are filtered after that row is picked.

```sql theme={null}
with balances_on_date as (
    select
        account_address,
        balance,
        row_number() over (partition by account_address order by balance_date desc) as recency
    from `functions.calculate_historical_eod_token_balances`(
        'ethereum',
        '0xdac17f958d2ee523a2206206994597c13d831ec7',
        'erc20'
    )
    where balance_date <= date '2026-06-30'
)

select
    account_address,
    balance
from balances_on_date
where recency = 1
  and balance > 0
order by balance desc
limit 100
```

**Holder concentration**

The holder set is every address holding the token on that date, so a share of supply is worked out over the whole set. [`dimensions.accounts`](https://tokenterminal.com/docs/catalog/accounts/registry) names the addresses somebody has attributed, so pools and receipt-token contracts read as themselves.

```sql theme={null}
with balances_on_date as (
    select
        account_address,
        balance,
        row_number() over (partition by account_address order by balance_date desc) as recency
    from `functions.calculate_historical_eod_token_balances`(
        'ethereum',
        '0x6c3ea9036406852006290770bedfcaba0e23a0e8',
        'erc20'
    )
    where balance_date <= date '2026-06-30'
),

holders as (
    select
        account_address,
        balance,
        row_number() over (order by balance desc) as holder_rank,
        sum(balance) over () as supply_held,
        count(*) over () as holders
    from balances_on_date
    where recency = 1
      and balance > 0
)

select
    holders.holder_rank,
    holders.account_address,
    accounts.contract_name,
    projects.name as project,
    holders.balance,
    holders.balance / holders.supply_held as share_of_supply,
    holders.holders as holders_on_date
from holders
left join `dimensions.accounts` as accounts
    on accounts.account_id = concat(holders.account_address, '-ethereum')
left join `dimensions.projects` as projects
    on projects.project_id = accounts.project_id
where holders.holder_rank <= 20
order by holders.holder_rank
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.