📝オンチェーン分析のためのプラットフォーム. チェーンに対して📝SQLでクエリを発行できる.

Dune

Basics

  • raw table: チェーンの生データ.
  • decorded table: 後処理済みのもの. Program/Funcitonごととか.

Dune API

API - Dune Docs

Pricing

  • free
    • 2500credits
  • analyst
    • 月額50ドルの課金でAPIからのquery実行可能
    • 4000credits

毎月2500 creditsの無料枠あり. 2500を超えたら10 creditsごとに0.3ドル=50円くらい.

  • execitions: 10 credits
  • datapoints: 1 credits

datapoints

rowsとcolsまたはデータ量から算出される値.

A datapoint can in most cases be thought of rows * columns with an additional limit of 100 avg bytes per cell in a set of results. This can be expressed as:

Datapoints = max(rows*columns, ceil(totalbytes/100))

https://dune.com/docs/api/faq/?h=datapoint#whats-a-datapoint


1000datapoints = 1 credits.

https://dune.com/docs/api/?h=credit#api-pricing

APIからのクエリ実行は無料でできない

defaultでperformanceがmedium=10credits消費する. 毎月2500 creditsが無料枠である.

You cannot run API executions on the free tier. Result returns an execution_id for the specified request.

クエリ実行をUIから手動実行してその結果をAPIで取得というものもある(自動化はできない).

ref. https://dune.com/docs/api/api-reference/execute-queries/execute-query-id/

Topics

export機能(csv/json)

課金しないとUIからのcsv exportはできない. APIの無料creditsの範囲でquery resultsをapiをつかってjson/csv保存. クエリ画面のエディタの右上のAPIを押すとurlがでてくる.

自動化するにはquery_idを指定してAPIでクエリを実行し, 結果をhttp getで取得する. SDKでもいけるけど簡単なのはcurlとか. importData 関数でGoogle Spreadsheetにもいける.

downloadではdatapoints でcreditsが消費する. credits=(rows * cols)/1000.

via curl

$ curl "https://api.dune.com/api/v1/query/<query_id>/results/csv?api_key=<api_key>" -o "output.csv"

API制限

  • 1回のAPI callで1GBまでDownload可能.
    • There is currently a 1GB limit, but there is a chance we reduce this overall or based on varying paid plan types.
  • defaultで2500000 datapointsまでの制限がかっている. optionsで制限を外すことはできる.
    • This api request would exceed your configured limits per request. Please visit your settings on dune.com and adjust your limits to continue usage.

Dune SQL

もともとDuneはPostgresをつかっていたが, 最適化のために独自DSLであるDune SQLを開発した.

普通のSQLでうまく動かなかったらドキュメント参照.

無料版と課金の比較

subscriptionsとcreditsがある. subscriptionは400ドルからなので大抵はcreditsで必要なだけ課金.


  • ダッシュボードは無料版だと1つのみprivateであとはpublic. 課金するとprivateたくさん.
  • 無料枠でのクエリは2分でタイムアウトする.

Dune with Solana

https://dune.com/docs/data-tables/raw/solana/


Solana Analytics Starter Guide (Part 1): Solana Instructions

solana.transacitons

ref. https://dune.com/docs/data-tables/raw/solana/transactions/

  • id: signaturesの先頭. signatureと同じ値.
  • balancesはカンマ区切りの文字列で変える

  • rawデータで情報量が多いので絞り込みが必要. logとかもはいってる.
  • solana.account_activityが処理された結果なのでこっちがいいかも.

solana.account_activity

ref. https://dune.com/docs/data-tables/raw/solana/account-activity/

  • address: Signer
  • token_balance_owner: associated account owner.
    • documentだとtoken_owner_addressとなってる. 仕様変更?
  • token_mint_address: mint address.
  • block_timeはTIMESTAMP型, block_dateはDATE型.

  • addressを指定すると一応 associated accountの取引も含めて関連するtxはすべて取得できる.
    • なぜならばSPL Tokensの取引の手数料で5000 lamportsが消費されるので.
    • ただし, token_balance_change, post_token_balanceは空なので損益計算では不向き.
    • 損益計算用途ではなく取引履歴の保存が目的ならばtxのリストは取得できるので最悪これでもいい.
      • solana.transactionsテーブルでsignerでもいい.
      • ただtransactionsテーブルは情報が多すぎる上にrawデータなので, block_time, signature, signer程度のSELECTがいい.
  • addressではなくtoken_balance_ownerを指定するととてもクエリが遅い.
    • addressカラムにはindexやなんらかの最適化がなされている可能性がある.
  • tx_idを指定してもとても遅い. tx_idの絞り込みはsolana.transactionsテーブルを使ったほうがいい.

SOL transactions

確定申告の帳簿保存用.

  • SOLはlamportで数値が表示されるのでdecimals=9で割る.
  • SOLの場合は tx_succss = falseでもトランザクション手数料がかかる.
SELECT
  block_time AS time,
  balance_change / 1e9 AS sol_change,
  post_balance / 1e9 AS sol_balance,
  address,
  tx_id
FROM
  solana.account_activity
WHERE
  (
    address = ''
    OR address = ''
    OR address = ''
  )
  AND block_date > DATE '2023-11-21'
ORDER BY
  1

SPL transactions

確定申告の帳簿保存用.

  • tx_success = TRUEを指定して成功したtxのみ絞り込み.
  • wSOLのtoken_balance_changeとSOLのbalance_change/1e9が一致する.
  • wSOL post_token_balanceとSOL post_balance/1e9とは一致しない.
  • addressの部分をtoken_owner_addressにするとwallet addressに紐づくassociated accountのtxがすべて取得可能. ただしクエリがおそすぎて実用的でない. addressを すべて列挙するのがいい.
SELECT
  block_time AS time,
  token_balance_change AS token_change,
  token_mint_address AS mint_address,
  token_balance_owner AS owner_address,
  address AS token_address,
  tx_id
FROM
  solana.account_activity
WHERE
  (
    address = ''
    OR address = ''
    OR address = ''
    OR address = ''
  )
  AND tx_success = TRUE
  AND block_date BETWEEN DATE '2023-11-20' AND DATE '2023-11-26'
ORDER BY
  1

2024版

  • 毎月のデータ量だとapi downloadできない(たぶん8MBの制限), 週次にわけてdownload.
  • credit消費はcolumn数に対して計算されるので可能な限りcolumn数を削減
    • blocktime
    • txid
    • change
  • SOL/USDCでわけてダウンロードすることでカラム削減. ファイル名でsol/usdcを区別.

SOL

SELECT
  block_time AS time,
  -- token_balance_change AS change,
  balance_change / 1e9 AS change,
  tx_id
FROM
  solana.account_activity
WHERE
  (
    -- address = 'hoge' -- SOL
    -- address = 'hoge2' -- wSOL
    address = 'hoge3' -- USDC
  )
  AND tx_success = TRUE
  AND block_date BETWEEN DATE '2024-11-25' AND DATE '2024-12-01'
ORDER BY
  1

USDC

SELECT
  block_time AS time,
  -- token_balance_change AS change,
  balance_change / 1e9 AS change,
  tx_id
FROM
  solana.account_activity
WHERE
  (
    address = 'hoge' -- SOL
    OR address = 'hoge2' -- wSOL
    -- address = 'hoge3' -- USDC
  )
  AND tx_success = TRUE
  AND block_date BETWEEN DATE '2024-11-25' AND DATE '2024-12-01'
ORDER BY
  1

2025検討

無料枠の範囲でDuneをつかってあとはStakeTaxをつかう二刀流戦略. 月額49ドルはちょっと高いかな…

SELECT
  block_time AS time,
  balance_change AS sol_change,
  token_balance_change AS spl_change, -- AS change,
  -- token_mint_address AS mint_address,
  tx_id
FROM
  solana.account_activity
WHERE
  tx_success = TRUE
  AND address = '' -- SOL for jito tip
  -- AND address = '' -- WSOL for arb
  -- AND address = '' -- USDC for arb
  AND block_date = DATE '2025-01-01'
  -- AND block_date BETWEEN DATE '2025-01-01' AND DATE '2025-01-01'
ORDER BY
  1

downloadデータ削減のためにcolumn厳選して、Python Scriptで変換によって補う方針.

  • address を SOL/WSOL/USDCの3つに分けることで、token_mint_addressは不要.
  • SOL aka. jito tipの場合は balance_changeに jito tip feeが入る.
  • WSOL/USDC はtoken_balance_changeの arb利益が入る.

Daily PnL

block_dateが20xx-xx-xxのDATE型になっている. たぶんインデックス処理もされていてこのカラムを活用するのが高速.

post_balance, post_token_balanceは日付をまたぐ判定が煩雑だったり, 大きな資金移動できれいなbarチャートにならないので外した.

SELECT
  DATE(block_time) AS date,
  SUM(balance_change) / 1e9 AS sol_change
FROM solana.account_activity
WHERE
  address = ''
GROUP BY
  DATE(block_date)
SELECT
  DATE(block_time) AS date,
  SUM(token_balance_change) AS token_change
ssFROM solana.account_activity
WHERE
  address = ''
  AND tx_success = TRUE
GROUP BY
  DATE(block_date)

Daily balance

https://dune.com/data/solana_utils.daily_balances

SELECT
  *
FROM solana_utils.daily_balances
WHERE
  address = TRY_CAST('wallet address' AS VARCHAR)
ORDER BY
  1 DESC

https://github.com/duneanalytics/spellbook/blob/3725c086774cf1fcb1d3459e1341761a75aa6a34/dbt_subprojects/solana/models/solana_utils/solana_utils_daily_balances.sql

Solana MEV Daily PnL for クリプタクト

for 💡クリプタクトカスタムファイル

この方式だとDEXに閉じたリバランスや資金移動は問題ないけど, CEX<->DEXのやりとりだとPnLでおかしなことになってしまう. この場合はTransfer Txを除外する必要があるものの, solana.account_activityでfilterに使えそうな情報はtx_idくらいしかないので, 送金したidを覚えておく必要がありそう. いい方法を検討中.

SOL

SELECT
  CONCAT(CAST(block_date AS VARCHAR), ' 23:59:59') AS Timestamp,
  CASE
    WHEN SUM(balance_change) >= 0 THEN 'BONUS'
    ELSE 'LOSS'
  END AS Action,
  'solana' AS Source,
  'SOL' AS Base,
  SUM(balance_change) / 1e9 AS Volume,
  '' AS Price,
  'JPY' AS Counter,
  0 AS Fee,
  'JPY' AS FeeCcy,
  'Solana MEV Daily SOL PnL' AS Comment
FROM
  solana.account_activity
WHERE
  (
    address = ''
    OR address = ''
    OR address = ''
  )
  AND block_date BETWEEN DATE '2023-11-18' AND DATE '2023-11-26'
GROUP BY
  block_date
ORDER BY
  1

SPL

SELECT
  CONCAT(CAST(block_date AS VARCHAR), ' 23:59:59') AS Timestamp,
  CASE
    WHEN SUM(token_balance_change) >= 0 THEN 'BONUS'
    ELSE 'LOSS'
  END AS Action,
  'solana' AS Source,
  CASE
    WHEN token_mint_address = 'So11111111111111111111111111111111111111112'
    THEN 'SOL'
    WHEN token_mint_address = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
    THEN 'USDC'
    WHEN token_mint_address = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'
    THEN 'USDT'
    ELSE token_mint_address
  END AS Base,
  SUM(token_balance_change) AS Volume,
  '' AS Price,
  'JPY' AS Counter,
  0 AS Fee,
  'JPY' AS FeeCcy,
  'Solana MEV Daily SPL PnL' AS Comment
FROM solana.account_activity
WHERE
  (
    address = ''
    OR address = ''
    OR address = ''
    OR address = ''
  )
  AND tx_success = TRUE
  AND block_date BETWEEN TRY_CAST('2023-11-18' AS DATE) AND TRY_CAST('2023-11-26' AS DATE)
GROUP BY
  block_date,
  token_mint_address
ORDER BY
block_date

🔗References