skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
162 lines (142 loc) • 7.79 kB
Markdown
## Contents
- 1. Dune Analytics SQL Queries
- Token Holder Analysis — balance ledger
- Token Holder Distribution — concentration / Nakamoto-style
- DEX Volume — use the curated dex.trades table
- Protocol TVL (simplified, flow-based)
- Whale Tracking
## 1. Dune Analytics SQL Queries
> **Engine:** Dune runs **DuneSQL** (Trino/Presto dialect) since the 2024 migration off Postgres. Use double quotes for identifiers (`"from"`, `"to"`), `from_hex`/`varbinary` for addresses, `bytearray_substring` for byte slicing, and DuneSQL date functions. Prefer **curated Spellbook tables** (`dex.trades`, `tokens.transfers`, `nft.trades`, `prices.usd`/`prices.minute`, `labels.*`, `tokens.erc20`) over raw decoded protocol event tables — Spellbook normalizes decimals, symbols, USD value, and multichain schemas, and is far less brittle than per-protocol `*_evt_*` tables whose names change between protocol versions.
### Token Holder Analysis — balance ledger
The correct pattern for an ERC-20 balance is a **normalized transfer ledger**: every transfer contributes `+value` to the recipient and `-value` to the sender; sum per address and keep positives. Always scale by real `decimals` and exclude the zero address (mint/burn sink) from the holder set. Prefer the curated `tokens.transfers` Spellbook table — it already normalizes amounts and works multichain.
```sql
-- Top 100 holders of USDC on Ethereum (USDC has 6 decimals)
WITH ledger AS (
-- inflows: recipient gains
SELECT "to" AS holder, amount_raw AS delta
FROM tokens.transfers
WHERE blockchain = 'ethereum'
AND contract_address = 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 -- USDC
UNION ALL
-- outflows: sender loses
SELECT "from" AS holder, -amount_raw AS delta
FROM tokens.transfers
WHERE blockchain = 'ethereum'
AND contract_address = 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
)
SELECT
holder,
SUM(delta) / 1e6 AS balance -- USDC decimals = 6
FROM ledger
WHERE holder <> 0x0000000000000000000000000000000000000000 -- drop mint/burn sink
GROUP BY holder
HAVING SUM(delta) > 0
ORDER BY balance DESC
LIMIT 100;
```
> If you must use raw decoded events instead of `tokens.transfers`, substitute `erc20_ethereum.evt_Transfer` with columns `"to"`, `"from"`, `value`, and join `tokens.erc20` to get `decimals` rather than hardcoding `1e6`/`1e18`.
### Token Holder Distribution — concentration / Nakamoto-style
Build the per-holder balance ledger **once**, then rank and bucket. The original version was wrong on two counts: it referenced the output alias `holder` inside its own aggregate (illegal — aliases are not visible in the expression that defines them), and it summed only `"to"` rows so outgoing transfers were never subtracted. This version fixes both.
```sql
WITH ledger AS (
SELECT "to" AS holder, amount_raw AS delta
FROM tokens.transfers
WHERE blockchain = 'ethereum'
AND contract_address = 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
UNION ALL
SELECT "from" AS holder, -amount_raw AS delta
FROM tokens.transfers
WHERE blockchain = 'ethereum'
AND contract_address = 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
),
balances AS (
SELECT holder, SUM(delta) / 1e6 AS balance
FROM ledger
WHERE holder <> 0x0000000000000000000000000000000000000000
GROUP BY holder
HAVING SUM(delta) > 0
),
ranked AS (
SELECT
holder,
balance,
ROW_NUMBER() OVER (ORDER BY balance DESC) AS rnk,
SUM(balance) OVER () AS circulating
FROM balances
)
SELECT
CASE
WHEN rnk <= 10 THEN 'Top 10'
WHEN rnk <= 50 THEN 'Top 11-50'
WHEN rnk <= 100 THEN 'Top 51-100'
ELSE 'Rest'
END AS tier,
COUNT(*) AS holders,
SUM(balance) AS total_balance,
SUM(balance) / MAX(circulating) * 100 AS pct_of_supply
FROM ranked
GROUP BY 1
ORDER BY MIN(rnk);
```
> `circulating` here is the on-ledger circulating supply (sum of positive balances), not max/total supply — state that explicitly when you publish. CEX hot wallets, bridges, and staking contracts inflate "top holder" concentration; label and optionally exclude them via `labels.all` before drawing conclusions about decentralization.
### DEX Volume — use the curated `dex.trades` table
Do **not** query per-pool/per-version event tables like `uniswap_v3_ethereum.Pair_evt_Swap` for volume — Uniswap V3 swaps are pool-based (not "Pair") and table names differ by protocol version, so they break constantly. Spellbook's `dex.trades` already aggregates every DEX/version across chains, normalizes decimals, and precomputes `amount_usd`. It also avoids the original query's price-join bug (joining on `symbol='ETH'` ignored chain and token address and could fan out rows).
```sql
-- Daily Uniswap volume on Ethereum (all versions), pool-level via dex.trades
SELECT
DATE_TRUNC('day', block_time) AS day,
COUNT(*) AS num_trades,
SUM(amount_usd) AS volume_usd
FROM dex.trades
WHERE blockchain = 'ethereum'
AND project = 'uniswap' -- omit for total cross-DEX volume
AND block_time >= NOW() - INTERVAL '30' DAY
AND amount_usd IS NOT NULL -- rows with no reliable price are dropped from $ volume
GROUP BY 1
ORDER BY 1;
```
> `dex.trades` is pool-level: a swap routed through 1inch/CoWSwap appears once per pool hop, so per-pool DEX volume is correct but user-intent volume is overstated. Use `dex_aggregator.trades` for aggregator trade intents (one row per intent), and never sum the two tables together (that double counts). If you need a token whose USD price is missing from `dex.trades`, join `prices.minute` on the full key (`ON p.blockchain = t.blockchain AND p.contract_address = t.token_bought_address AND p.timestamp = DATE_TRUNC('minute', t.block_time)`), never on `symbol` alone.
### Protocol TVL (simplified, flow-based)
```sql
-- Cumulative net flow for a lending protocol. Aggregate to daily FIRST, then
-- run the running total over the daily grain (a window over raw rows gives a
-- per-row total, not per-day). See §6 for the balance-based caveat.
WITH daily AS (
SELECT
DATE_TRUNC('day', evt_block_time) AS day,
SUM(CASE WHEN event_type = 'deposit' THEN amount_usd ELSE -amount_usd END) AS net_usd
FROM protocol_events
WHERE evt_block_time >= NOW() - INTERVAL '90' DAY
GROUP BY 1
)
SELECT day, SUM(net_usd) OVER (ORDER BY day) AS cumulative_tvl
FROM daily
ORDER BY day;
```
### Whale Tracking
```sql
-- Large ERC-20 transfers (>$1M) in the last 24 hours, any token
SELECT
tr.evt_block_time,
tr."from",
tr."to",
tr.value / POWER(10, t.decimals) AS amount,
tr.value / POWER(10, t.decimals) * p.price AS value_usd,
t.symbol
FROM erc20_ethereum.evt_Transfer tr
JOIN tokens.erc20 t
ON t.contract_address = tr.contract_address
AND t.blockchain = 'ethereum'
-- price keyed on full (blockchain, contract, minute); INNER JOIN so untradeable
-- tokens with no price are excluded instead of producing NULL > 1e6 = false silently
JOIN prices.minute p
ON p.blockchain = 'ethereum'
AND p.contract_address = tr.contract_address
AND p.timestamp = DATE_TRUNC('minute', tr.evt_block_time)
WHERE tr.evt_block_time >= NOW() - INTERVAL '24' HOUR
AND tr.value / POWER(10, t.decimals) * p.price > 1000000
ORDER BY value_usd DESC
LIMIT 50;
```
> Note `prices.minute` is the current Spellbook minute-resolution price table (the older `prices.usd` alias may still resolve). Many "whale" transfers are exchange/bridge plumbing — join `labels.all` on `"from"`/`"to"` to filter out CEX, bridge, and known protocol addresses before calling a wallet a whale.
---