I keep a short list of wallets I care about, and the first thing I do with any new one is check whether it moves before or after price does. You cannot answer that by staring at Etherscan. You need the address next to a chart, the transfers rolled up into a running balance, and the whole thing refreshing on its own so you are not re-running SQL every morning. Dune does all of that, and you can get a usable version standing in an afternoon if you skip the parts that do not matter yet.
The mistake most people make is starting from a blank query. Dune already has thousands of public queries, and the transfer-tracking patterns are mostly solved. So I start by forking, get something on screen, and only then rewrite the parts that are specific to what I am actually watching.
Fork first, understand second
Search Dune for something close to what you want, like an ERC-20 balance query or a token flow dashboard, open it, and hit Fork. You now own an editable copy. Run it as-is before you change a single line. Half the value of forking is seeing which tables the author used and how they joined them, and you only learn that by watching the query execute and reading the columns that come back.
The tables you care about live in the decoded and token schemas. On Ethereum, erc20_ethereum.evt_Transfer gives you every ERC-20 transfer with from, to, contract_address, and a raw value. There is also tokens.transfers, a cross-chain table that already normalizes transfers across networks, which saves you from handling each chain separately. If the forked query uses one and you would rather use the other, that is your first real edit. I usually reach for the normalized table when I want one dashboard covering several chains, and the raw event table when I am doing something precise on a single chain.
One thing that trips people up early is decimals. The value in a raw transfer event is an integer in the token's base units, so a token with 18 decimals shows numbers with eighteen extra zeros. Divide by power(10, decimals) before you show anything to a human. Dune's token metadata tables carry the decimals so you can join them in rather than hard-coding, which matters the moment you extend the query to a second token that does not use 18.
Turn a watchlist into SQL
A watchlist is just a set of addresses you filter on. The clean way to manage it is a query parameter or a small inline list you can edit in one place, so adding a wallet does not mean rewriting five WHERE clauses. I keep mine as a comma-separated parameter and reference it everywhere downstream.
The core idea for net position is that every transfer is a credit to the receiver and a debit to the sender. So you union two views of the same transfer table, one where your tracked address is the to field counted as positive, and one where it is the from field counted as negative, then sum. In rough shape it looks like this:
- Select transfers where
tois in your watchlist, tag the amount positive. - Union with transfers where
fromis in your watchlist, tag the amount negative. - Group by address and token, order by block time, and take a running sum with a window function to get balance over time.
That running sum is the whole trick. A window like sum(signed_amount) over (partition by wallet, token order by block_time) turns a pile of individual transfers into a balance curve you can actually chart. Once you have that curve, net position change over any window is just the last value minus the first, which is the number I care about far more than the raw balance.
Two failure modes to watch for. First, wrapped and bridged versions of a token have different contract addresses, so a wallet that looks like it dumped everything may have just wrapped it. If you are tracking economic exposure and not one specific contract, decide up front whether to collapse those into one asset or keep them separate, because the balance curve looks completely different depending on the choice. Second, exchange deposit addresses and router contracts will pollute your view if you track a wallet that interacts with them heavily. A transfer to a known exchange hot wallet is a real signal, so I keep a small labels table and annotate rather than filter, because knowing a whale sent tokens to a CEX is often the most useful row in the query.
Chart it and let it refresh
Dune's visualizations sit on top of the query result, so once your balance-over-time query returns clean rows, adding a chart is a few clicks. A line chart with block time on the x-axis and running balance on the y-axis, one series per wallet, is the view I look at most. I add a second chart for net flow per day, which is a bar chart of signed amounts bucketed by day, because a flat balance line can hide a wallet that is churning hard in both directions underneath.
Assemble the charts into a dashboard and set the queries to refresh on a schedule so the whole thing stays current without you touching it. Refresh cadence is a tradeoff. Whale positions do not usually change minute to minute, and Dune query execution has real cost, so hourly or a few times a day is plenty for most tracking. I only push toward tight refresh intervals when I am watching something time-sensitive, and even then I would rather get an alert than stare at a dashboard.
Extend it without rewriting it
The reason this pattern is worth learning once is that almost none of it is token-specific. To track a different token, you change the contract address filter. To track a different chain, you point at that chain's transfer table or use the normalized cross-chain one. To track a new wallet, you add it to the parameter. The SQL that computes signed amounts and running balances does not move.
A few habits that keep the dashboard honest as it grows:
- Always divide by decimals at the edge, never carry raw base units into a chart.
- Label known addresses instead of silently filtering them, so exchange flows stay visible.
- Store your watchlist in one parameter, not scattered across clauses.
- Keep a raw-transfers query alongside the aggregated one, so when a number looks wrong you can drop to the individual transfers and see what happened.
That last one saves the most time. Aggregations lie in quiet ways, and having the un-aggregated transfers one click away is how you catch a missing decimals join or a bridged token before you act on a bad reading. Dune is excellent for the exploratory pass, and once a pattern proves out I tend to fold the same on-chain wallet signals into how we watch positions inside Blockcircle so I am not living in a browser tab. Start with the fork, get a balance curve on screen, and grow it one address at a time. The version that tracks one wallet and the version that tracks fifty are almost the same query.