Skip to main content
Two tables sit behind every tokenized-asset cap table, and they are read together. facts_asset_tokens.balance_changes_daily records one row for each day, each deployment and each holding account whose balance moved, with that day’s net change, in whole token units. Balances themselves are not stored. The balance functions add these rows up at query time.

Sparse, so a balance is an accumulation

A day with no row means the balance did not move that day, not that it was zero. So a holder’s balance on a date is the sum of every row at or before that date, never a lookup of that date. Ask for one day in isolation and you get that day’s movement, which is rarely the question. The functions exist so this is not something every reader has to get right.

Read the coverage and attribution before the numbers

One thing gates a cap table: whether a ledger summed from transfer legs can describe the deployment at all. Coverage answers that per deployment, and it answers about the mechanism rather than about activity. So a deployment with no rows here is one of two things, and they are not the same. Either coverage refuses it, in which case it names the reason, or coverage serves it and the deployment has simply never been transferred. An asset issued but never moved has an empty cap table, which is the correct answer rather than a missing one. The curated functions read coverage and refuse with that reason instead of returning a figure they cannot stand behind.

Tables

facts_asset_tokens.balance_changes_daily records one row per (timestamp, chain_id, token_address, account_address).
ColumnTypeDescription
timestampTIMESTAMPDay the balance moved. The partition column.
chain_idSTRINGChain the deployment sits on.
asset_idSTRINGAsset the deployment belongs to.
token_idSTRINGKey of the deployment, formatted {token_address}-{chain_id}.
token_addressSTRINGContract address of the deployment. Filter this and chain_id together.
account_addressSTRINGThe chain’s native holding account: a wallet on EVM chains, a token account on Solana.
account_ownerSTRINGThe holder. Equal to account_address where the chain does not distinguish them, and the wallet behind the token account where it does. Group by this for a cap table. Group by account_address instead and one wallet’s several Solana token accounts each count as a separate holder.
balance_deltaBIGNUMERICNet balance movement that day, signed, in whole token units.

What is not here, and why

The families currently declared out, and absent from this table rather than approximated:
  • Chains without a balance arm. Every asset on them, whatever its mechanics.
  • Assets whose balances revalue without moving. A rebasing token grows every holder at once with no transfer to observe, so a ledger summed from transfer legs holds the value each transfer had and never revalues it.
  • Assets hand-declared out with measured evidence. Two cases are worth knowing about generally: a chain that migrated balances between token standards without emitting the credit, and a token whose confidential transfers hide the amount from the event that would record it.
Two mechanisms are worth naming, because both are refused rather than approximated. A Solana scaled UI amount multiplies a raw balance by a multiplier the mint can change, and an interest-bearing rate accrues into the holder-facing amount with no transfer to observe. A Dinari dShare revalues the same way, through a multiplier the issuer writes. In every case the balance moves without a transfer, so a ledger summed from transfer legs cannot see it, and the deployment is declared out until a model can convert it.

Notes

  • Filter token_address and chain_id together. They are the cluster keys; a filter on token_id alone reaches neither.
  • balance_delta is BIGNUMERIC throughout, deliberately. Float arithmetic leaves dust where a closed position should read exactly zero, and dust counts as a holder.