Tue Apr 1
  :  :  

Token holders of $X also hold $Y

This showcases relations between token holders. Given a token $X, which other tokens are the holders of $X holding?

We dive deeper into this through a Dune query below. The process is straight-forward, although compute heavy. We query all the token holders for a specified token, and then proceed to re-build their ERC-20 token balances based on all of their historical ERC-20 token Transfer events. And lastly, we sort by number of token holders.

WITH transfers AS (
  SELECT
    "from" AS address,
    "from",
    "to",
    contract_address,
    value
  FROM erc20_ethereum.evt_Transfer evt
  WHERE contract_address = {{token}}

  UNION ALL

  SELECT
    "to" AS address,
    "from",
    "to",
    contract_address,
    value
  FROM erc20_ethereum.evt_Transfer evt
  WHERE contract_address = {{token}}
),
token_holders AS (
  SELECT
    tf.address,
    tf.contract_address,
    SUM(
      CASE
        WHEN "to" = tf.address THEN CAST(tf.value AS DOUBLE) / POW(10, COALESCE(t.decimals, 18))
        WHEN "from" = tf.address THEN -CAST(tf.value AS DOUBLE) / POW(10, COALESCE(t.decimals, 18))
        ELSE CAST(0 AS DOUBLE)
      END
    ) AS balance
  FROM transfers tf
  LEFT JOIN tokens.erc20 t ON t.contract_address = tf.contract_address
  GROUP BY tf.address, tf.contract_address, t.symbol
),
holders AS (
  SELECT DISTINCT address
  FROM token_holders
  WHERE balance > CAST(1e-4 AS DOUBLE)
),
all_transfers AS (
  SELECT *
  FROM erc20_ethereum.evt_Transfer t
  INNER JOIN holders h ON t."from" = h.address

  UNION ALL

  SELECT *
  FROM erc20_ethereum.evt_Transfer t
  INNER JOIN holders h ON t."to" = h.address
),
final_filtered_holders AS (
  SELECT
    address,
    COUNT(*) AS count
  FROM all_transfers
  GROUP BY address
  HAVING COUNT(*) < 30000
),
filtered_transfers AS (
  SELECT *
  FROM erc20_ethereum.evt_Transfer t
  INNER JOIN final_filtered_holders h ON t."from" = h.address

  UNION ALL

  SELECT *
  FROM erc20_ethereum.evt_Transfer t
  INNER JOIN final_filtered_holders h ON t."to" = h.address
),
filtered_transfers_ordered AS (
  SELECT *
  FROM filtered_transfers
  ORDER BY evt_block_number ASC, evt_index ASC
),
balances AS (
  SELECT
    fto.address,
    fto.contract_address,
    t.symbol,
    SUM(
      CASE
        WHEN fto."to" = fto.address THEN CAST(fto.value AS DOUBLE) / POW(10, COALESCE(t.decimals, 18))
        WHEN fto."from" = fto.address THEN -CAST(fto.value AS DOUBLE) / POW(10, COALESCE(t.decimals, 18))
        ELSE CAST(0 AS DOUBLE)
      END
    ) AS balance
  FROM filtered_transfers_ordered fto
  LEFT JOIN tokens.erc20 t ON t.contract_address = fto.contract_address
  GROUP BY fto.address, fto.contract_address, t.symbol
),
balances_filtered AS (
  SELECT *
  FROM balances
  WHERE balance > CAST(0 AS DOUBLE)
),
token_correlations AS (
  SELECT
    contract_address,
    symbol,
    COUNT(*) AS count,
    SUM(balance) AS cumulative_balance
  FROM balances_filtered
  GROUP BY contract_address, symbol
),
main_correlation AS (
  SELECT count
  FROM token_correlations
  WHERE contract_address = {{token}}
  LIMIT 1
),
correlations AS (
  SELECT
    tc.*,
    CAST(tc.count AS DOUBLE) / CAST(mc.count AS DOUBLE) AS correlation
  FROM token_correlations tc, main_correlation mc
),
spam_tokens AS (
  SELECT contract_address
  FROM (
    VALUES
      (0x471c3a7f132bc94938516cb2bf6f02c7521d2797),
      (0x0d3716e3e411af431a6e87e715d4b05bbcd67000),
      (0x82dfdb2ec1aa6003ed4acba663403d7c2127ff67),
      (0x8053aff5111eaeabaa32f06242825c70c939e4f7),
      (0x20a21170d3bf388cbe2ac34643b5180a26e68331),
      (0x388bc6728b3cab9e93bd79105d6ecb3db8431918),
      (0x34278f6f40079eae344cbac61a764bcf85afc949),
      (0x1883a07c429e84aca23b041c357e1d21a2b645f3),
      (0xd88e87cd53d5b3c88c07bdb4715a39b75d6e7870),
      (0x1412eca9dc7daef60451e3155bb8dbf9da349933),
      (0x68ca006db91312cd60a2238ce775be5f9f738bba),
      (0xc1c8c49b0405f6cffba5351179befb2d8a2c776c),
      (0x67542502245eb5df64ef7ea776199ceb79401058),
      (0x54fd62228c6e1234fd5fded28555ca963dcf6d26),
      (0x2d262c7faa8e03a86d6634bdc6178b3e06cd690b),
      (0x643695d282f6ba237afe27ffe0acd89a86b50d3e),
      (0x956f824b5a37673c6fc4a6904186cb3ba499349b),
      (0xcf39b7793512f03f2893c16459fd72e65d2ed00c),
      (0x265befe2b1a0f4f646dea96ba09c1656b74bda91),
      (0xb688d06d858e092ebb145394a1ba08c7a10e1f56),
      (0xe89c2dccfa045f3538ae3ebbfff3daec6a40a73a),
      (0x2f87445c3c81a5b268adab1915235a94aa27bf54),
      (0x5d80a8d8cb80696073e82407968600a37e1dd780),
      (0x27ed436e67515c1dd33633537eebd59207c51806),
      (0x3ed525fa1f10889dbf55515d1a436d91680d6c54),
      (0x120aa018634f555484c088c8da80f75aa07e004f),
      (0x622651529bda465277cb890ef9176c442f42b338),
      (0x7a6b87d7a874fce4c2d923b09c0e09e4936bcf57),
      (0xad9f583f255ea7fba4321f84cba57c3bbd2f0687)
  ) AS temp_table(contract_address)
)
SELECT
  c.*
FROM correlations c
LEFT JOIN spam_tokens s ON c.contract_address = s.contract_address
WHERE s.contract_address IS NULL
ORDER BY c.count DESC
LIMIT {{_limit}};

An example below where we run the query on the $CULT token, and include the top 40 tokens that are held by the $CULT token holders.

In the bar chart below we see $USDC, $WETH, $USDT—but also tokens such as $MOG, $SPX and $PEPE. A correlation of 0.3 implies 30% of the $CULT holders hold the token, and the number on top shows the total holder count.

0.00.10.20.30.40.50.60.70.80.91.0↑ correlationCULTETHGUSDCWETHBURGERSUSDTXCTOSCATMogBULIBULIMOMOMILADY2.0BITCOINPEPEENAFUMO2.0MOJOPEPEWUKONGIINUNOMICSSPXDAIEIGENMOGIMOGIREZPEIPEIEWAPUSOSDEEPSEEK AIBLURBERRY AILOOKSDOGEVISTA TRUMPFUKIFUKIAPEBABYPOCHITAPEPESEUSQEQEWBTCMATICsymbol10,6713,0142,4881,8411,6591,6481,2311,1891,1601,022969919915833776725692668660603589585576526467436430426424407406403403400396394394389387372