Настройка аналитики блокчейн-проекта (Dune Analytics)
Dune Analytics — это SQL-интерфейс поверх декодированных блокчейн-данных. Вместо того чтобы разворачивать собственный индексер и писать парсеры событий, вы пишете SQL-запросы к готовым таблицам, где транзакции и события уже декодированы по ABI. Для большинства аналитических задач это в разы быстрее, чем собственный стек.
Dune поддерживает Ethereum, Arbitrum, Optimism, Base, Polygon, BSC, Solana, и несколько десятков других сетей в рамках одного SQL-окружения.
Структура данных в Dune
Таблицы организованы в несколько уровней:
Raw данные — сырые блокчейн данные без декодирования:
-
ethereum.blocks— блоки -
ethereum.transactions— транзакции -
ethereum.logs— сырые события (topics + data как байты) -
ethereum.traces— internal calls
Decoded данные — события и вызовы декодированные по ABI:
-
uniswap_v3_ethereum.Factory_evt_PoolCreated— событие PoolCreated от Uniswap V3 Factory -
erc20_ethereum.evt_Transfer— все ERC-20 Transfer события - Формат:
{protocol}_{network}.{contract}_{type}_{event/function}
Spells — аналитические таблицы, подготовленные сообществом:
-
dex.trades— все DEX сделки по всем сетям в единой схеме -
tokens.erc20— метаданные токенов -
nft.trades— NFT сделки
Декодирование своего контракта
Чтобы события вашего контракта появились в decoded таблицах — нужно загрузить ABI:
- Открыть
dune.com/contracts/new - Указать сеть и адрес контракта
- Вставить ABI (JSON)
- Дождаться одобрения (обычно 1–3 рабочих дня)
После декодирования появится таблица вида yourproject_ethereum.YourContract_evt_YourEvent.
Написание запросов: практические паттерны
Базовый анализ токена
-- Количество уникальных держателей ERC-20 токена
WITH transfers AS (
SELECT
"to" AS address,
value AS amount
FROM erc20_ethereum.evt_Transfer
WHERE contract_address = 0xYourTokenAddress
UNION ALL
SELECT
"from" AS address,
-value AS amount
FROM erc20_ethereum.evt_Transfer
WHERE contract_address = 0xYourTokenAddress
AND "from" != 0x0000000000000000000000000000000000000000
)
SELECT
COUNT(DISTINCT address) FILTER (WHERE balance > 0) AS holders
FROM (
SELECT address, SUM(amount) AS balance
FROM transfers
GROUP BY address
)
TVL (Total Value Locked) протокола
-- TVL через декодированные Deposit/Withdraw события
SELECT
DATE_TRUNC('day', evt_block_time) AS day,
SUM(SUM(CAST(amount AS DOUBLE) / 1e18)) OVER (ORDER BY DATE_TRUNC('day', evt_block_time)) AS cumulative_tvl_eth
FROM yourproject_ethereum.Vault_evt_Deposit
GROUP BY 1
ORDER BY 1
Объём торгов через DEX
-- Использование Spell таблицы dex.trades
SELECT
DATE_TRUNC('week', block_time) AS week,
blockchain,
SUM(amount_usd) AS volume_usd,
COUNT(*) AS trades_count
FROM dex.trades
WHERE token_bought_address = 0xYourTokenAddress
OR token_sold_address = 0xYourTokenAddress
AND block_time >= NOW() - INTERVAL '90' day
GROUP BY 1, 2
ORDER BY 1 DESC
Cohort-анализ пользователей
-- Первая транзакция пользователя = дата вступления в когорту
WITH first_tx AS (
SELECT
"from" AS user,
MIN(DATE_TRUNC('week', evt_block_time)) AS cohort_week
FROM yourproject_ethereum.YourContract_evt_SomeAction
GROUP BY 1
),
weekly_activity AS (
SELECT
"from" AS user,
DATE_TRUNC('week', evt_block_time) AS active_week
FROM yourproject_ethereum.YourContract_evt_SomeAction
GROUP BY 1, 2
)
SELECT
f.cohort_week,
DATE_DIFF('week', f.cohort_week, w.active_week) AS weeks_since_join,
COUNT(DISTINCT w.user) AS retained_users
FROM first_tx f
JOIN weekly_activity w ON f.user = w.user
GROUP BY 1, 2
ORDER BY 1, 2
Dune API: встраивание данных в продукт
Для отображения аналитики в вашем приложении — Dune API:
const DUNE_API_KEY = process.env.DUNE_API_KEY!
// Запуск запроса и получение результата
async function getDuneQueryResult(queryId: number): Promise<DuneResult> {
// Запустить выполнение
const execRes = await fetch(`https://api.dune.com/api/v1/query/${queryId}/execute`, {
method: 'POST',
headers: { 'X-Dune-API-Key': DUNE_API_KEY },
body: JSON.stringify({ performance: 'medium' }), // medium / large
})
const { execution_id } = await execRes.json()
// Ждать завершения
let status = 'QUERY_STATE_PENDING'
while (['QUERY_STATE_PENDING', 'QUERY_STATE_EXECUTING'].includes(status)) {
await new Promise(r => setTimeout(r, 2000))
const statusRes = await fetch(
`https://api.dune.com/api/v1/execution/${execution_id}/status`,
{ headers: { 'X-Dune-API-Key': DUNE_API_KEY } }
)
const statusData = await statusRes.json()
status = statusData.state
}
// Получить результаты
const resultRes = await fetch(
`https://api.dune.com/api/v1/execution/${execution_id}/results`,
{ headers: { 'X-Dune-API-Key': DUNE_API_KEY } }
)
return resultRes.json()
}
Для dashboard с обновлением раз в час — кэшировать результаты, не запускать запрос на каждый HTTP-запрос клиента.
Параметризованные запросы
Dune поддерживает параметры в запросах — удобно для универсальных дашбордов:
-- В запросе используем {{parameter}}
SELECT *
FROM dex.trades
WHERE token_bought_address = {{token_address}}
AND block_time >= NOW() - INTERVAL '{{days}}' day
// При вызове через API передаём параметры
const body = {
query_parameters: {
token_address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
days: 30,
},
performance: 'medium',
}
Альтернативы Dune для специфических задач
| Задача | Инструмент |
|---|---|
| Real-time данные (< 1 мин задержка) | The Graph (subgraph) |
| SQL + быстрее Dune | Flipside Crypto, Allium |
| Собственный индексер | Envio, Ponder, Goldsky |
| NFT аналитика | NFTGo, Reservoir |
| Cross-chain данные | Nansen, Messari |
Dune имеет задержку от нескольких минут до часа на бесплатном плане. Для real-time метрик в production — The Graph или собственный индексер.
Типичный дашборд для DeFi протокола
Набор метрик, который стоит настроить с первого дня:
- Daily/Weekly Active Users (уникальные адреса)
- TVL в USD с разбивкой по токенам
- Объём транзакций (count + USD value)
- Revenue (комиссии протокола)
- Retention cohorts
- Top users / whales
- Gas spent by protocol users
Создание базового дашборда с 5–7 метриками, декодирование контракта, настройка Dune API для встраивания в продукт: 1–2 дня.







