In this guide, we're going to look at how to create a view of balances at a block level and daily level.
By using the snapshot data in ethereum.core.fact_token_balances we can create a block level view of balances or a daily view. The approach for both methods is similar, but we can start at a block level.
Please note: Balances data is very large. Queries without where filters on block_number of block_timestamp may take a long time. Please do your best to only query what you really need. However, we do realize that some use cases require full history.
We are going to use the same USDC-WETH Uniswap v3 pool from the prior example. We'll start by pulling the decimal transformed balances for the pool from the last 7 days.
select block_number, block_timestamp, user_address, contract_address, c.symbol, balance / pow(10, c.decimals) as balancefrom ethereum.core.fact_token_balances bleft join ethereum.core.dim_contracts c on b.contract_address = c.addresswhere user_address ='0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640'and block_timestamp >= current_date() -7and contract_address in ('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2','0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' )
The next step involves filling all the gaps in blocks with the prior value. This pool has a lot of volume as of writing, so there is not much filling to do, as there is already almost a swap every block. However, something like an EOA (wallet) will have much less volume, and therefore has more filling to do. We will fill by block_number here, but if you only needed daily balances, you could fill by block_timestamp::date.
Once we have our spine of block_number, user_address, contract_address, and symbol, we can left join in our balances data, and fill the gaps with the prior valid value. Let's do this with a few CTEs.
with balances as (select block_number, block_timestamp, user_address, contract_address, c.symbol, balance / pow(10, c.decimals) as balancefrom ethereum.core.fact_token_balances bleft join ethereum.core.dim_contracts c on b.contract_address = c.addresswhere user_address ='0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640'and block_timestamp >= current_date() -7and contract_address in ('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2','0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' )),-- create a spine of all the possible combosspine as (select block_number, block_timestamp, user_address, contract_address, symbolfrom ethereum.core.fact_blocks bjoin (selectdistinct contract_address, user_address, symbolfrom balances ) on1=1where block_number between (selectmin(block_number)from balances )and (selectmax(block_number)from balances )),-- join all options with transfer based data and fill gaps block_level_balances as (select s.block_number, s.block_timestamp, s.user_address, s.contract_address, s.symbol, b.balance,LAST_VALUE(b.balance ignorenulls) over(PARTITIONBY s.user_address, s.contract_addressORDER BY s.block_number ASCrowsunboundedpreceding ) as balance_filledfrom spine sleft join balances b using (block_number, user_address, contract_address))select*from block_level_balances;
This gives us the WETH and USDC balance, per block, for the Uniswap V3 pool. We could take this a step further by filtering to just the last balace per day using the following final step.
select block_timestamp :: dateas block_date, block_number, user_address, contract_address, symbol, balance_filled as balancefrom block_level_balances qualify (row_number() over (partitionby user_address, contract_address, block_dateorder by block_number desc ) =1 )order by block_date, user_address, contract_address
If you wanted to analyze the total supply of a token, you would want to remove the user_address filter. There are many possibilities with these tables, but remember to use as many filters as possible for optimal performance.