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

# Transfers

> Every fungible token movement, on every chain we decode.

One row per fungible token movement: a transfer, a mint or a burn, on every chain we decode. `amount_raw` is the amount in the token's smallest unit, so divide it by `10^decimals` from [`dimensions.tokens`](https://tokenterminal.com/docs/catalog/tokens/registry) to read whole tokens.

## Columns

<table>
  <thead>
    <tr><th width="230">Column</th><th width="120">Type</th><th>Description</th></tr>
  </thead>

  <tbody>
    <tr><td>`partition_key`</td><td>`TIMESTAMP`</td><td>The month the row belongs to, as its first instant. The table is partitioned on it, so bound it alongside `block_timestamp`.</td></tr>
    <tr><td>`block_timestamp`</td><td>`TIMESTAMP`</td><td>Time of the block containing the movement.</td></tr>
    <tr><td>`block_number`</td><td>`INT64`</td><td>Block height, or the slot on Solana.</td></tr>
    <tr><td>`transaction_hash`</td><td>`STRING`</td><td>Transaction the movement belongs to. Null on `hydradx`, where a transfer can be emitted by a block hook rather than by a transaction.</td></tr>
    <tr><td>`transaction_index`</td><td>`INT64`</td><td>Position of the transaction in its block. Solana only: a decoded Transfer event on the log-shaped chains is the log, and the transaction around it lives in that chain's own transaction table.</td></tr>
    <tr><td>`outer_event_index`</td><td>`INT64`</td><td>Enclosing instruction position on Solana. A log has one position and an instruction has two, so this is null on the log-shaped chains, and null on a top-level Solana instruction, which has nothing enclosing it.</td></tr>
    <tr><td>`event_index`</td><td>`INT64`</td><td>Position within the transaction: the log index, or the instruction index on Solana.</td></tr>
    <tr><td>`chain_id`</td><td>`STRING`</td><td>Chain the movement happened on.</td></tr>
    <tr><td>`token_type`</td><td>`STRING`</td><td>Token standard: `erc20`, `spl` or `trc20`.</td></tr>
    <tr><td>`token_id`</td><td>`STRING`</td><td>Join key to [`dimensions.tokens`](https://tokenterminal.com/docs/catalog/tokens/registry) and the asset tables, which are laid out on it: `{token_address}-{chain_id}`.</td></tr>
    <tr><td>`token_address`</td><td>`STRING`</td><td>The token, and the leading cluster key: filter on this with `chain_id` to read one token, and on this alone to read the address across every chain in one pass. `erc20` addresses are stored lowercase and every other standard as the chain writes it, `spl` and `trc20` being base58. Null on the Solana transfers whose token account the upstream decode could not resolve, which leaves `token_id` null with it.</td></tr>
    <tr><td>`event_type`</td><td>`STRING`</td><td>`transfer`, `mint` or `burn`. Solana's token program names it; on the log-shaped chains it is read from the EIP-20 zero address, so a burn sent to some other dead address reads as `transfer`.</td></tr>
    <tr><td>`from_address`</td><td>`STRING`</td><td>Account debited: a wallet on the log-shaped chains, an SPL token account on Solana. Null on a mint.</td></tr>
    <tr><td>`to_address`</td><td>`STRING`</td><td>Account credited: a wallet on the log-shaped chains, an SPL token account on Solana. Null on a burn.</td></tr>
    <tr><td>`to_owner`</td><td>`STRING`</td><td>Holder behind `to_address`, on Solana transfers, where one wallet can own several token accounts. On the log-shaped chains `to_address` is already the holder. Null on exactly the rows where `token_address` is null, since one upstream decode resolves a token account to both its mint and its owner, and null on Solana mints and burns, whose upstream tables hold no owner column.</td></tr>
    <tr><td>`authority_address`</td><td>`STRING`</td><td>Account that authorised the instruction. Solana only.</td></tr>
    <tr><td>`transaction_signer`</td><td>`STRING`</td><td>Signer of the enclosing transaction. Solana only.</td></tr>
    <tr><td>`executing_account`</td><td>`STRING`</td><td>Program that executed the instruction. Solana only.</td></tr>
    <tr><td>`amount_raw`</td><td>`STRING`</td><td>Amount in the token's smallest unit, as a decimal string, because a uint256 exceeds every BigQuery numeric type. Divide by `10^decimals`.</td></tr>
    <tr><td>`surrogate_key`</td><td>`BYTES`</td><td>Row identity: a 128-bit hash of the columns that define the grain, stable across rebuilds. Compare it with `from_hex`.</td></tr>
  </tbody>
</table>

## Sample queries

> [!WARNING]
>
> This table is large and split by month. Bound `partition_key` and filter `chain_id` on every query, or you read the whole table and the whole table is billed to you.

**One token, one day**

`token_address` and `chain_id` are the cluster keys and `partition_key` names the month, so the three together read only the blocks this token occupies. Cast `amount_raw` to `BIGNUMERIC` and divide by `10^decimals` from `dimensions.tokens` for whole tokens.

```sql theme={null}
select
    transfers.block_timestamp,
    transfers.transaction_hash,
    transfers.from_address,
    transfers.to_address,
    cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
from `facts_tokens.transfers` as transfers
join `dimensions.tokens` as tokens
    using (token_id)
where transfers.token_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
  and transfers.chain_id = 'ethereum'
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-02')
limit 100
```

**One token, every chain**

Leaving `chain_id` out reads the address on every chain it is deployed at in one pass, which the clustering is built for. `partition_key` still bounds the months the query reads.

```sql theme={null}
select
    transfers.chain_id,
    count(*) as movements,
    count(distinct transfers.from_address) as senders
from `facts_tokens.transfers` as transfers
where transfers.token_address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-08')
group by transfers.chain_id
order by movements desc
```

**Largest movements**

`amount_raw` is a decimal string, so ordering on it directly sorts lexically. Order on the decimal-corrected amount instead, as here.

```sql theme={null}
select
    transfers.block_timestamp,
    transfers.transaction_hash,
    transfers.from_address,
    transfers.to_address,
    cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
from `facts_tokens.transfers` as transfers
join `dimensions.tokens` as tokens
    using (token_id)
where transfers.token_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
  and transfers.chain_id = 'ethereum'
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-08')
order by amount desc
limit 20
```

**One token on Solana**

An SPL mint address is base58 and is matched as the chain writes it. `from_address` and `to_address` are token accounts here, `to_owner` is the wallet behind the credited one, and [`dimensions.accounts`](https://tokenterminal.com/docs/catalog/accounts/registry) names the addresses somebody has attributed.

```sql theme={null}
select
    transfers.block_timestamp,
    transfers.transaction_hash,
    transfers.from_address,
    transfers.to_address,
    transfers.to_owner,
    cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
from `facts_tokens.transfers` as transfers
join `dimensions.tokens` as tokens
    using (token_id)
where transfers.token_address = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
  and transfers.chain_id = 'solana'
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-02')
limit 100
```

**Movements in order**

`outer_event_index` is null where a chain has one nesting level, so `nulls first` groups those rows together and `event_index` decides inside the group. The same ordering holds on every chain.

```sql theme={null}
select
    transfers.block_timestamp,
    transfers.transaction_hash,
    transfers.outer_event_index,
    transfers.event_index,
    transfers.from_address,
    transfers.to_address,
    cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
from `facts_tokens.transfers` as transfers
join `dimensions.tokens` as tokens
    using (token_id)
where transfers.token_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
  and transfers.chain_id = 'ethereum'
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-02')
order by transfers.block_number,
         transfers.outer_event_index nulls first,
         transfers.event_index
limit 100
```

**Supply movements**

A mint fills `to_address` and a burn fills `from_address`, so `coalesce` picks whichever side the row has. Supply read off these rows stays exact even for a rebasing token, whose holder balances accrue between movements.

```sql theme={null}
select
    transfers.block_timestamp,
    transfers.event_type,
    transfers.transaction_hash,
    coalesce(transfers.to_address, transfers.from_address) as account,
    cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
from `facts_tokens.transfers` as transfers
join `dimensions.tokens` as tokens
    using (token_id)
where transfers.token_address = '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo'
  and transfers.chain_id = 'solana'
  and transfers.event_type in ('mint', 'burn')
  and transfers.partition_key >= timestamp('2026-08-01')
  and transfers.partition_key < timestamp('2026-09-01')
  and transfers.block_timestamp >= timestamp('2026-08-01')
  and transfers.block_timestamp < timestamp('2026-08-08')
order by transfers.block_timestamp
limit 100
```

**Net flow per account**

Splitting each movement into its credit and its debit and summing them replays a balance where a balance moves by movement alone. A rebasing token accrues between movements, and an SPL mint configured for confidential transfers moves value the instruction keeps private, so [the balance functions](https://tokenterminal.com/docs/catalog/tokens/balances.md) inherit the same limit.

```sql theme={null}
with legs as (
    select
        transfers.to_address as account_address,
        cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals) as amount
    from `facts_tokens.transfers` as transfers
    join `dimensions.tokens` as tokens
        using (token_id)
    where transfers.token_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
      and transfers.chain_id = 'ethereum'
      and transfers.partition_key >= timestamp('2026-08-01')
      and transfers.partition_key < timestamp('2026-09-01')
      and transfers.block_timestamp >= timestamp('2026-08-01')
      and transfers.block_timestamp < timestamp('2026-08-08')

    union all

    select
        transfers.from_address,
        -cast(transfers.amount_raw as bignumeric) / pow(10, tokens.decimals)
    from `facts_tokens.transfers` as transfers
    join `dimensions.tokens` as tokens
        using (token_id)
    where transfers.token_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
      and transfers.chain_id = 'ethereum'
      and transfers.partition_key >= timestamp('2026-08-01')
      and transfers.partition_key < timestamp('2026-09-01')
      and transfers.block_timestamp >= timestamp('2026-08-01')
      and transfers.block_timestamp < timestamp('2026-08-08')
)

select
    account_address,
    sum(amount) as net_amount
from legs
where account_address is not null
group by account_address
order by net_amount desc
limit 20
```


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