facts.asset_token_senders records one row for each day, each deployment and each address that sent it. There is no number on the row: the row itself is the fact that this address sent this token that day. Every active-user count in the daily asset tables is worked out from this set.
facts.asset_token_senders_hll holds a compact summary of the same addresses, one summary per day per deployment. It uses the same key columns, so the two tables line up row for row.
Why counting addresses is different
Most numbers can be added up. Counts of distinct addresses cannot. Say 100 addresses sent USDC on Monday and 100 sent it on Tuesday. The number of addresses that sent USDC over those two days is not 200. Anyone who sent on both days has been counted twice, and the two counts do not tell you how many that was. The same problem turns up across chains, across deployments and across assets. So the dailyuser_* columns in the daily asset tables are right for the day they describe, and no use at all as ingredients for a wider figure.
That is what the second table is for. A sketch is a small summary of a set of addresses: a few kilobytes, however large the set. It has the one property a count lacks: two sketches can be combined into a single sketch before anything is counted. Combine the sketches for a stretch of days, a group of chains or a basket of assets, and you have one sketch covering all those addresses, with the repeats already collapsed. Count that, and every address counts once.
What you give up is exactness. A count taken off a sketch is a very good estimate rather than an exact number. The technique is called HyperLogLog++, which is where the _hll in the table name comes from, and ours are built at precision 15, close enough that their counts agree with the user_* figures published in the daily tables.
Tables
- Senders
- Senders HLL
facts.asset_token_senders records one row per (timestamp, asset_token_id, sender_address). This table answers the questions a count cannot: how far two assetsβ users overlap, or whether the addresses that showed up in March are still active in June. Those need the addresses themselves, not a total.| Column | Type | Description |
|---|---|---|
timestamp | TIMESTAMP | Day the address sent the deployment. The partition column. |
asset_token_id | STRING | Deployment the address sent, formatted {asset_id}-{token_address}-{chain_id}. |
asset_id | STRING | Asset the deployment belongs to. |
token_id | STRING | Key of the deployment in dimensions.tokens. |
chain_id | STRING | Chain the deployment lives on. |
symbol | STRING | Ticker symbol of the asset. |
asset_type | STRING | Classification of the asset. |
bridged_status | STRING | Issuance classification of the deployment: native, bridged or unclassified. |
token_address | STRING | Address of the token contract. |
sender_address | STRING | Address that sent the token that day. |
Sample queries
- One deployment, one day
- Monthly active senders
- Weekly senders by chain
Count the addresses that sent USDC on Ethereum on one day. An exact distinct count over one deployment and one day reads a single partition.
Notes
The sketch table answers how many, at any level you ask it, and it is a fraction of the size. The address table answers who, which is what overlap and retention questions need. A count off combined sketches is an estimate, off by well under a percent at this precision.count(distinct sender_address) over the addresses is exact, and you pay for the extra data it reads.