Meme Atlas AZ · top-holders-set-definition
前 100 大持有地址:集合怎麼界定,集中度就怎麼讀
同一批餘額,各家算出的前十大佔比為什麼對不上:先問這份名單由誰選、按哪一層排。
- 發布
- 更新
- 編輯責任
- Meme Atlas AZ
一張「前 100 大持有地址」的表格擺在眼前,最先被讀出來的通常是兩個百分比:第一名佔多少,前十名合計佔多少。幾乎所有關於集中度的說法都從這兩個數字起步。問題是它們並非從鏈上直接取回,而是從一份已經被人選過的名單上算出來的。
先擺一個可以自己動手驗的反例。下面這組數字純粹為了演示算術而編,不對應任何代幣,也不對應任何一條鏈上的實際狀態。
假設一份名單只有十行,餘額由大到小排開,佔比分別是 40%、17%、9%、8%、7%、6%、5%、4%、2%、2%。這張表上的百分比,分母是這十行的合計,不是總供應量——這兩個分母不是同一回事,後面會單獨再講一次。底下三種改法裡,有一種連這個分母一起換掉了,那正是它能給出另一組讀數的原因。「第一名佔四成、前兩名合計 57%」,這是這張表給出的第一個讀數。
現在一個字節的鏈上資料都不動,只改做名單的人事先立下的規則。
第一種改法:第一行其實是一個池合約,而這份名單決定把池合約剔除。剩下九行重新歸一化,原本 17% 的那一行變成第一名,佔比是 17÷60,約 28%;前兩名合計從 57% 掉到約 43%。同一批餘額,剔與不剔給出的是兩個方向相反的印象——而這張表本身回答不了哪一個更接近事實。
第二種改法:這十行取自 Solana,其中第二、第三、第四、第五行的 owner 欄位指向同一個錢包。按 owner 合併之後名單剩下七行,合併出來的那一位佔 17+9+8+7=41%,直接越過原本的第一名;前兩名合計從 57% 變成 81%。行數少了三行,頭部卻重了二十四個百分點。
第三種改法:把餘額為零、但歷史上收過轉帳的地址也算進「持有地址」。這一次要留意的恰恰是佔比一個字都沒動——零餘額對分母沒有貢獻,十行還是那十行,41% 也好 28% 也好都不會因此變化。變的是另一個讀數:「這個代幣有多少個持有地址」從十變成幾百甚至幾千。而「前十名佔多少」與「有幾個持有者」這兩個數字,經常被擺在同一段話裡當成同一件事的兩面。
三次改動,三份互不相同的讀數,而它們共用同一批餘額;其中還有一次改的根本不是佔比,是「持有者」這個詞涵蓋誰。產生差別的地方全部在名單做出來之前——在某個人決定了哪些東西算一行、哪些不算的那一刻。
所以在問一個代幣集中不集中之前,有個更靠前的問題要先有答案:這份名單是誰、按什麼規則選出來的。底下順著這條線往下走。排在這裡的順序有點刻意,先擺反例、後講標準,是因為先讀條文的人常常把條文當成名單的保證,而條文恰恰什麼都沒保證。
合約那一側沒有一個方法能回答「誰在持有」
EIP-20 把一個代幣合約該提供的東西列完了:方法是 name、symbol、decimals、totalSupply、balanceOf、transfer、transferFrom、approve、allowance 九個(其中 name、symbol、decimals 標為 OPTIONAL,標準明寫其它合約「MUST NOT expect these values to be present」),事件只有 Transfer 與 Approval 兩個。整份標準裡沒有任何一項回傳持有者名單、持有者數量,或任何形式的地址枚舉。
更值得看清楚的是 balanceOf 的簽名:`balanceOf(address _owner) public view returns (uint256 balance)`,標準給的說明文字是 "Returns the account balance of another account with address `_owner`"。這個方法要求呼叫者手上先有一個地址,才問得出這個地址有多少。想知道「有哪些地址」,它幫不上忙。
於是「前 100 大」這份名單只能從另一條路做出來:有人把這個合約發過的 Transfer 事件從頭重放一遍,自行累計每個地址的進出,再按自己定的規則排序、決定取到第幾名為止。這中間的每一步都是人做的選擇,選取規則握在做名單的人手上,不寫在鏈上。
標準沒有定義「持有者」這個詞,也沒有規定索引者該怎麼行為。這一句值得照原樣記住:不能從標準推出任何一份具體名單是怎麼做的,更不能把某一家的做法說成「大家都這樣算」。
零值轉帳讓「出現過」和「有餘額」分成兩個集合
重放事件的人會馬上撞上一條標準寫得很清楚、讀的時候卻常被跳過的規定。
Transfer 事件的要求是 "MUST trigger when tokens are transferred, including zero value transfers";transfer 與 transferFrom 兩處都另有註記 "Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event"。數量為零的轉帳,事件照發。
後果相當直接。把事件流裡出現過的地址收集起來,得到的是「歷史上被寫進過某筆轉帳的地址」;此刻餘額大於零的地址是另一個集合,比前者小,小多少無法從事件筆數反推。兩個集合都能拿來當名單的底,做出來的表卻不是同一張。
標準還替創造留了一條慣例:"A token contract which creates new tokens SHOULD trigger a Transfer event with the `_from` address set to 0x0 when tokens are created."。零地址會以 from 的身分出現在事件流裡。它算不算一個地址、要不要出現在名單上,標準一個字都沒說,做名單的人得自己決定一次。
ethereum.org 的 ERC-20 說明頁逐字轉載了同一組方法與事件,把這個標準提供的能力歸納成四件事:帳戶之間的轉移、查某個帳戶當前的餘額、查網路上的總供應量、授權第三方動用某個帳戶的一定額度。沒有一件是列出帳戶。
名單上那一行,不保證背後站著一個人
ethereum.org 同一頁有一段 Web3.py 範例,用來示範怎麼呼叫 balanceOf。範例傳進去的那個「帳戶」地址,頁面自己的註解標明它是一個 Uniswap V2 池合約。
這件事值得停一下。官方文件示範「查一個帳戶的餘額」的時候,順手選的那個帳戶就是一個池。可見「帳戶餘額」這個說法本身,並不保證另一端站著一個人。
池是怎麼持幣的,Uniswap 的開發者文件寫得很直白:協議管理的是流動性池,每個池由兩種 ERC-20 代幣的儲備構成,並依池的狀態更新價格;任何人都可以把代幣對存進池裡成為流動性提供者;使用者換幣的時候,換的是池的儲備。
儲備以池合約的名義持有,於是池會以一個地址出現在名單上,而那一行底下是任意多個存款人。把那一行讀成「某個持有者持有這麼多」,算出來的集中度就多了一塊本該散開的量。
這裡不能再往前多走一步。文件沒有給任何池的餘額或佔比,也沒有涉及其他自動做市實作或其他鏈上的池,所以本文不對「池在名單裡佔多少」作任何推論。範例選了哪個地址,只說明文件作者的寫法,不構成任何統計主張。
想把池那一行拆開,三個版本不是同一套手法
知道那一行是池,下一步自然是問能不能拆開,還原成背後的存款人。同一份 Uniswap 文件在這裡給出一個很具體的困難。
文件寫的是,流動性份額怎麼記帳按版本不同:v2 鑄造同質的 ERC-20 池代幣,代表儲備的按比例份額;v3 與 v4 讓每個流動性提供者選定一個價格區間,v3 的部位以 ERC-721 形式由 NonfungiblePositionManager 表示,v4 的部位由 PositionManager 管理,內部記帳使用 ERC-6909。
三種憑證形態,三套讀法。v2 那一條路看起來最好走——池代幣是同質的 ERC-20,照理再列一次持有名單就行;做過一次才會發現它只是把原來那個問題原封不動往後推了一層,新名單同樣得重新界定集合。v3 要處理的是一堆各自帶價格區間的 NFT 部位,v4 的記帳在 PositionManager 內部;拿 v2 的辦法去套,算出來的東西跟原本要問的問題已經對不上。
名單上那一行看起來只是一行,往下拆的成本卻取決於它是哪個版本的池,而這件事不寫在那一行裡。
Solana 這一側,名單列的根本不是錢包
換一條鏈,名詞整套換掉,集合界定的難處跟著換了形狀。
Solana 官方文件的要點這樣界定:Mint Account 代表某一個特定代幣,存放總供應量、鑄造權限這類全域資訊;Token Account 追蹤某個特定 owner 對某個特定 mint 的持有情況;Associated Token Account 則是位址由 owner 與 mint 位址推導出來的一個 Token Account。Token Account 的欄位為 mint、owner、amount、delegate、state、is_native、delegated_amount、close_authority。
關鍵的一句毫無歧義:一個錢包想持有某個代幣就需要一個對應的 token account,錢包位址被設為該 token account 的 owner;而每個錢包可以對同一個代幣(mint)持有多個 token account,一個 token account 則只能有一個 owner、只能裝一個 mint 的單位。
餘額存在 token account 上面。一份按地址排序的名單,列出來的因此是 token account,不是人。同一個人可以在名單上佔據好幾個位置,把名單的列數當成持有人數,讀出來的分散度會偏高。要從名單回到人,必須多讀一層:token account 的 owner 欄位才指向控制者。這一層在名單表面上看不見,得逐行去取。
還有兩條容易被混為一談的。其一,Associated Token Account 只是位址推導方式,文件明講「ATA 就是一個位址特定的 token account」,它不是身分憑證。其二,建立 ATA 需要一筆可退還的最低餘額充當帳戶儲存租金,文件寫明「指令的付費者出這筆錢,即使這個 ATA 由另一個錢包持有」。一個 token account 存在,跟它現在有沒有餘額,是兩件不相干的事,出租金的人甚至可以不是 owner。
這一節能寫到的最後一句是:必須先問這份名單按哪一層排。這一頁沒有說明任何一家瀏覽器的持有榜是否按 owner 合併,本文因此不寫成「瀏覽器都不合併」。
沒列進去的那一塊,是一個未枚舉的差額
前 100 名以外還有多少、落在多少個地址上,這份名單沒有回答。
名單能給的只有前 100 行各自的餘額。拿總供應量減去這 100 行的合計,得到一個差額。這個差額是一個數字,不是一份名單,它沒有行數、沒有排序,也沒有身分。
有兩種寫法把這個差額變成了它不是的東西。一種是當成零,直接說剩下的可以忽略;另一種是給它安上身分,說剩下的是散戶。兩種都超出了名單支持的範圍。按前面幾節的理由,那一塊裡可能有池合約,可能有同一個人的多個 token account,可能有餘額為零但歷史上出現過的地址(取決於這份名單怎麼界定),也可能有前面沒提到的其他形態。
分母也要單獨確認一次:總供應量取自哪個欄位、在哪個區塊高度或 slot 上讀的、跟名單是不是同一個時點。
本文沒有核到的四件事
以下四項界定規則本文都沒有取得官方說明,列在這裡是讓後面重讀的人知道該往哪裡補,不是拿來當已知條件用。
第一,各家區塊瀏覽器的持有榜是怎麼算的。Etherscan 的說明中心(info.etherscan.com)在 2026-09-19 以站內檢索查過,沒有找到說明 token holders 名單如何構成的條目,命中的是代幣資訊提交指南、ERC-721 是什麼這類文章。這句話陳述的是這一次檢索的結果,不是在描述那家服務的算法。所以「有沒有排除合約地址、是否按 owner 合併、零餘額地址是否計入」這幾個問題,本文一家都沒有答案,也不替任何一家描述它的算法。要用哪一家的名單,這幾個問題得逐家去問。
第二,託管餘額與跨鏈橋鎖倉地址。名單上有一類地址代表的是很多人的存款或很多人的鎖倉,而不是一個人的持有,這件事本文沒有取得任何官方文件。因此只寫得出一句:要判斷某個地址屬不屬於這一類,得另有依據。不點名任何平台,也不聲稱某一類地址一定屬於其中之一。
第三,零地址與公開的銷毀地址在各家名單裡是否被計為持有者。未核。這是一條要先確認的選取規則,不是一個可以預設的答案;預設它被排除,跟預設它被計入,會得出不同的分母。
第四,Solana 上各家瀏覽器是否按 owner 合併 token account。未核,理由見上一節:官方文件沒有說明任何一家的聚合方式。
這四項不是可有可無的註腳。開頭那個反例已經演示過:界定規則換一個答案,出來的可能是一組不同的百分比,也可能百分比分毫未動、換掉的是「持有者有幾個」那個數字。兩種情況都足以讓兩份讀數對不上。
讀數對不上的時候,先問集合有沒有換
隔一段時間重查同一個代幣,兩次讀數不一樣,最順手的解釋是分布變了。這個解釋要站得住,得先排除另一個更常見的可能:兩次用的並不是同一個集合。
集合可能換掉的地方,前面幾節裡都出現過了。名單的來源換了一家;同一家改了排除規則;一次按 token account 排、一次按 owner 合併;一次算進零餘額地址、一次沒算;兩次的觀察時點不落在同一個區塊高度或 slot 上。這裡面任何一件變動,百分比都會跟著動,而鏈上的分布可以完全沒變。
所以一次讀數要留下什麼,標準只有一個:把這一次的選取規則原樣寫在數字旁邊,寫到足以讓另一個人照同樣的規則重做一遍、得到同一組數字。做不到這一點,那個百分比就是一個沒有定義域的數;下次拿它比較,比到的多半是兩套規則之間的差別。
至於集中度本身說明了什麼,那是另一個問題,而且它排在這個問題後面。名單沒有界定清楚之前,關於集中度的任何說法都缺一個前提。
常見問題
想直接問合約「現在有多少個持有者」,有沒有這樣一個方法?
沒有。EIP-20 的九個方法與兩個事件裡沒有任何一項做地址枚舉,balanceOf 也要求呼叫者先有一個地址才問得出餘額。持有者數量與持有者名單都是有人重放 Transfer 事件、自行累計與排序之後的產物,規則在做名單的人手上。
名單上一行一行的地址,可以當成一個一個的人來數嗎?
不行,而且兩個方向的偏差都存在。一行可能是池合約,那一行底下是任意多個存款人——官方範例傳進 balanceOf 的那個地址,按頁面自己的註解就是一個 Uniswap V2 池。另一頭,在 Solana 上一個錢包可以對同一個 mint 持有多個 token account,同一個人因此能佔據好幾行。
同一個代幣的「前十大佔比」,在不同地方看到的數字對不上,該信哪一個?
那個百分比是名單的函數,不是代幣的函數,對不上本身不奇怪。各家是否排除合約地址、是否按 owner 合併、零餘額地址算不算、取到第幾名為止、在哪個高度上取的,都可能不同。各家做法本文未核,也不替任何一家描述算法。
前 100 名以外的那一塊,能不能當成散戶持有?
不能。那是一個從總供應量減出來的未枚舉差額,沒有行數也沒有身分,裡面可能有池合約,可能有同一個人的多個 token account。把一個數字讀成一個群體,名單支持不了。
既然知道名單上有池,為什麼不把池那一行拆開再算一次?
因為拆法不通用。Uniswap 文件說明流動性份額的記帳按版本不同:v2 是同質的 ERC-20 池代幣,v3 的部位是 ERC-721,v4 由 PositionManager 以 ERC-6909 做內部記帳。拆到一半遇上別的版本就得整個換方法,這正是那一塊差額補不上的原因之一。
內容來源
- EIP-20: Token Standard發布者: Ethereum Improvement Proposals查閱日期:
- ethereum.org — ERC-20 Token Standard發布者: ethereum.org查閱日期:
- Solana Docs — Tokens發布者: Solana Foundation查閱日期:
- Uniswap Developers — How Uniswap Works發布者: Uniswap查閱日期:
- Etherscan Information Center發布者: Etherscan查閱日期: