Token 供应量数据集(Token Supply)
来源:schema/clickhouse/derived/019_token_supply.sql。 面向内部用 SQL 追踪和分析各 ERC-20 代币每日发行量(Mint)、销毁量(Burn)、每日净增量(Net Mint)以及历史累计流通供应量(Cumulative Supply)的团队。
来源:schema/clickhouse/derived/019_token_supply.sql。
面向内部用 SQL 追踪和分析各 ERC-20 代币每日发行量(Mint)、销毁量(Burn)、每日净增量(Net Mint)以及历史累计流通供应量(Cumulative Supply)的团队。
1. 概述与核心功能
以太坊与 EVM 兼容链上,代币的总供应量(totalSupply)是合约状态变量,标准 ERC-20 没有独立的"每日供应量快照"事件。
本项目通过解析 {db}.erc20_transfers 中的零地址转账:
- Mint(铸造 / 发行):
from = 0x0000000000000000000000000000000000000000 - Burn(销毁):
to = 0x0000000000000000000000000000000000000000 AND from != 0x0000000000000000000000000000000000000000
在每个已收盘的 UTC 日历日(toDate(block_timestamp) < today())执行幂等刷新,计算当日发行额、销毁额、净发行额,并使用时间窗口函数累加自创世以来的全部净发行额,得到每日收盘时的历史累计代币供应量(cumulative_supply)。同时提供实时全量视图与链上 spot-check 脚本,用于对齐节点 eth_call totalSupply()。
2. 架构设计与正确性规则
2.1 为什么采用收盘日幂等刷新(Option (b)),而不是插入触发物化视图
根据项目架构规范与正确性准则:
- 原始表
{db}.erc20_transfers是ReplacingMergeTree(version, is_deleted)。在断点续跑(backfill resume)时会重叠插入重复数据(相同排序键与版本),在发生链重组(reorg)时会写入墓碑行(is_deleted = 1,更高版本)。 - 普通的插入触发物化视图(Insert-trigger MV)对每批物理 INSERT 执行增量
sum()/count()。该机制在后台去重 merge 之前触发,无法识别重复插入与重组回退,会导致严重双重计数(double-counting)。 - 因此,本数据集严格遵循 CORRECTNESS RULE 方案 (b):
- 目标表
{db}.token_daily_supply由可刷新物化视图(REFRESH EVERY 1 DAY OFFSET 1 HOUR RANDOMIZE FOR 10 MINUTE)驱动。 - 仅对已过去的完整收盘日(
toDate(block_timestamp) < today())进行聚合。 - 读取数据源时强制使用
FINAL并过滤is_deleted = 0,保证重复版本与重组墓碑均已折叠清除。 - 在 ClickHouse 26.x 中,未带
APPEND的可刷新物化视图在每次调度触发时对目标表执行整表原子替换(Atomic Swap),天然完全幂等,杜绝一切历史漂移。
- 目标表
3. 金额精度与类型规范(Money-Safety)
- 绝对禁止浮点数:严禁在代币金额、发行量、销毁量计算中使用
Float32/Float64。 - 原始数量保持 UInt256:
mint_amount和burn_amount严格使用UInt256存储链上原始数量(Wei),无任何提前除以 $10^{decimals}$ 的缩放,保证精度零损失。 - 净变化量与累计量使用有符号 Int256:
$$\text{net_mint_amount} = \text{toInt256}(\text{mint_amount}) - \text{toInt256}(\text{burn_amount})$$
代币在某一天销毁量大于铸造量是常见且合法的经济行为(如大额赎回销毁),每日净增量可能为负数,必须使用有符号整数。合法代币总量远在 $2^{255}$ 范围以内,
toInt256转换精确无溢出风险。 - Fail-closed 异常大额过滤(amount >= 2^255):
遵循
027_erc20_balances.sql既有惯例,显式增加AND amount < bitShiftLeft(toUInt256(1), 255)。恶意 Honeypot 或垃圾代币铸造天文数字(如type(uint256).max)会导致sumIf发生 $2^{256}$ 回绕并在转为Int256时变为大负数,污染发布数据。此类异常值直接在入库前 fail-close 丢弃(dropped, not recorded wrong)。 - 时间窗口累计(Window Sum)与创世回填说明:
精确累加该代币从链历史上第一次铸造直到当日收盘的总量。注意:若sum(toInt256(mint_amount) - toInt256(burn_amount)) OVER ( PARTITION BY token ORDER BY day ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_supply{db}.erc20_transfers尚未回填至创世区块(genesis),历史铸造未入库而在窗口期内发生销毁,可能使未完全回填的代币暂时呈现负数cumulative_supply(与027_erc20_balances.sql相同,属于源数据回填范围特性,源表完整回填后自然准确)。
4. 链上真实语义与边界情况(Edge Cases)
在 Robinhood Chain(chain id 4663)真实链上数据与节点 RPC 验证中,观测并处理了以下典型场景:
4.1 Uniswap V2 配对初始化永久锁定流动性(Transfer(0, 0, 1000))
- 现象:Uniswap V2 Pair 初始化首次添加流动性时,会调用
_mint(address(0), MINIMUM_LIQUIDITY)永久锁死 1,000 Wei 的 LP 代币。该操作触发的事件为from = 0x00...00且to = 0x00...00,金额为1000。 - 合约行为:Pair 合约内部执行
totalSupply = totalSupply + 1000,并记录在balanceOf[address(0)] = 1000,合约的totalSupply()确实包含了这 1,000 Wei。 - 本方案处理:
若仅简单判断
to = 0计为销毁,则该笔交易同时记为 +1000 和 -1000,相互抵消导致累计量少算 1,000 Wei。 因此销毁条件显式限制为to = 0x00...00 AND from != 0x00...00,使得Transfer(0, 0, 1000)正确被计为铸造入库,累计值与链上totalSupply()精确至个位数(Wei)完全对齐。
4.2 构造函数未发事件的初始铸造(Constructor Unminted Supply)
- 现象:少数早期或非标准代币合约在
constructor中直接给特定地址赋值状态变量balanceOf[deployer] = INITIAL_SUPPLY; totalSupply = INITIAL_SUPPLY;,未触发emit Transfer(address(0), ...)。 - 表现:此类代币从 Transfer 日志计算的
cumulative_supply会小于节点eth_call totalSupply()。Spot-check 脚本对此类代币会自动打标为DIFF_UNMINTED。
4.3 销毁至黑洞地址(Dead Address Burn)
- 现象:部分项目通过向
0x000000000000000000000000000000000000dead等非零地址转账实现"社区销毁"。 - 表现:因其未调用标准
_burn(即未转入零地址),合约状态变量totalSupply()并未减少。本数据集遵循 ERC-20 规范,仅将转至零地址(address(0))视为正式销毁。
4.4 普通转账至零地址 vs 真正 _burn
- 现象:部分简易合约的
transfer函数未对接收方为address(0)做特殊拦截与totalSupply扣减。用户转入零地址只是转移了持有者,totalSupply()并没有变化。 - 表现:Spot-check 脚本会自动对比并标明
DIFF_BURN_TRANSFER(transfer to 0x0 did not reduce totalSupply on-chain)。
5. 数据表与视图结构
5.1 {db}.token_daily_supply(按天历史供应量表)
CREATE TABLE IF NOT EXISTS {db}.token_daily_supply
(
token FixedString(20),
day Date,
mint_count UInt64,
burn_count UInt64,
mint_amount UInt256,
burn_amount UInt256,
net_mint_amount Int256,
cumulative_supply Int256,
refreshed_at DateTime('UTC')
)
ENGINE = ReplacingMergeTree(refreshed_at)
PARTITION BY toYYYYMM(day)
ORDER BY (token, day);| 字段 | 类型 | 说明 |
|---|---|---|
token | FixedString(20) | 代币合约地址(二进制,可用 hex() 输出) |
day | Date | 已收盘的 UTC 日历日 |
mint_count | UInt64 | 当日铸造事件笔数(from = 0x00...00) |
burn_count | UInt64 | 当日销毁事件笔数(to = 0x00...00 AND from != 0x00...00) |
mint_amount | UInt256 | 当日总铸造数量(未缩放整数,单位为代币原始最小单位 Wei) |
burn_amount | UInt256 | 当日总销毁数量(未缩放整数,单位为代币原始最小单位 Wei) |
net_mint_amount | Int256 | 当日净铸造数量(mint_amount - burn_amount,可为负) |
cumulative_supply | Int256 | 截至当日收盘的历史累计代币供应量(时间窗口求和) |
refreshed_at | DateTime('UTC') | 视图或回填任务最近刷新时间 |
5.2 {db}.v_token_latest_supply(收盘日最新供应量摘要视图)
直接读取 token_daily_supply FINAL,提供每个代币在最近一个收盘日的全局聚合快照(总发行、总销毁、最新累计供应量)。查询开销极小。
5.3 {db}.v_token_live_supply(全链实时供应量视图)
直接读取 {db}.erc20_transfers FINAL,包含今日尚未收盘的最新实时区块数据,供随时随地对齐节点最新高度的 eth_call totalSupply()。
6. Spot-Check 抽检脚本(spot_check_token_supply.py)
仓库提供专用自动化验证脚本:scripts/derived/spot_check_token_supply.py。
运行方式
# 环境变量指定连接信息
export CH_URL="http://127.0.0.1:8123"
export CH_USER="indexer"
export CH_PASSWORD="indexer"
export CH_DB="robinhood"
export RPC_URL="https://5.9.43.222/<KEY>"
# 1. 批量抽检活跃代币
python3 scripts/derived/spot_check_token_supply.py --ch-db robinhood --limit 20
# 2. 检查指定代币合约
python3 scripts/derived/spot_check_token_supply.py --ch-db robinhood --token 0x5ebc7d0f2d01ed9ffe3f0617d30cb09664a17777
# 3. 切换检查模式(live: 包含今日实时转账;closed: 仅检查已收盘日)
python3 scripts/derived/spot_check_token_supply.py --ch-db robinhood --mode closed --limit 207. 本地验证(Verification & Evidence)
在本地独立 ClickHouse 实例上建立独立临时库 wt44_token_supply,使用预编译 release 二进制回填区块 [39250642, 39255642](2026-08-18 已收盘日,5,000 个区块,共 60,222 笔交易、179,209 条日志、72,152 笔 ERC-20 转账)。
7.1 验证用例 1:代币在其生命周期内完全包含在抽样区间内且无后续转账
在此区间内创建且无后续转账的代币,ClickHouse 中计算的 cumulative_supply 与节点真实 eth_call totalSupply() 达成 100% 精确匹配(精确到个位数 Wei):
代币地址 (token) | 首次出现区块 | ClickHouse cumulative_supply | 节点 RPC totalSupply() | 结果 |
|---|---|---|---|---|
0x5ebc7d0f2d01ed9ffe3f0617d30cb09664a17777 | 39252430 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x1829cf9dca23913693eddc58423f489fe005cccc | 39253464 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x4d22495cdbc7ed0cb4b692ca942dbe9e48257777 | 39251547 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x2f7ee2d4265b0d9c808b506c10c2f7aeb7717777 | 39250831 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x55f1d365370fa1d3e7a0bb7f68016d22f0bdcccc | 39251015 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x941959ee6048d1743f2382b1bb1b80d000727777 | 39255022 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0xd2d60ade6a0459f4c0abb0bf57e27bd50b7c7777 | 39253422 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0xd66d81e5af56c5c6870d4252566169adce52cccc | 39251567 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0xf2f1dcbcd3d36030415e4a7922b3b6ebe7d97777 | 39255041 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0xfc8da382f4816e032427024e76b1c7e9c6107777 | 39255046 | 1000000000000000000000000000 | 1000000000000000000000000000 | EXACT MATCH |
0x1e1a7cbde7fdefbcbc7c549d509ad1cf35d92909 | 39252350 | 0 | 0 | EXACT MATCH |
7.2 验证用例 2:Uniswap V2 Pair 复杂铸造与销毁
在生产环境区块 72045539 至 72045627 追踪代币 0xa5ef7104629a38633fc453308fd97f9068ab6e25:
- 事件 1(块 72045539,log 25):
from = 0, to = 0, amount = 1000(初始化锁死) - 事件 2(块 72045539,log 26):
from = 0, to = user, amount = 999999999999999999000 - 事件 3(块 72045627,log 1):
from = 0, to = user2, amount = 13123678961433 - 事件 4(块 72045627,log 2):
from = contract, to = 0, amount = 999999999999999999000(赎回销毁)
计算: $$\text{总发行} = 1000 + 999999999999999999000 + 13123678961433 = 1000000013123678961433$$ $$\text{总销毁} = 999999999999999999000$$ $$\text{净累计量} = 1000000013123678961433 - 999999999999999999000 = 13123678962433$$
调用节点 RPC:
eth_call(0xa5ef7104629a38633fc453308fd97f9068ab6e25, "0x18160ddd") 返回:
0x00000000000000000000000000000000000000000000000000000bef98390301 = 13123678962433。
两边数值完全一致,精确到个位。
8. 安装与补算
8.1 安装步骤
- 确认前置表
{db}.erc20_transfers已建好并已回填(参见001_erc20_transfers.sql)。 - 执行 SQL 文件(替换
{db}为真实库名,如robinhood): 若通过 HTTP 接口执行,请将文件内的各条 SQL 语句以分号切分后逐条 POST。sed 's/{db}/robinhood/g' schema/clickhouse/derived/019_token_supply.sql | clickhouse-client --multiquery - 文件内已包含一次性全量历史回填
INSERT INTO {db}.token_daily_supply ...,执行完即已包含当前所有历史已收盘日的代币供应量统计。
8.2 定时刷新与运维监控
- 视图
mv_token_daily_supply声明了: 在每天 UTC 01:00(±10分钟内随机抖动)自动触发刷新,以整表原子替换方式更新全部已收盘日数据。REFRESH EVERY 1 DAY OFFSET 1 HOUR RANDOMIZE FOR 10 MINUTE - 手动立即触发刷新:
SYSTEM REFRESH VIEW robinhood.mv_token_daily_supply; SYSTEM WAIT VIEW robinhood.mv_token_daily_supply; - 查看刷新状态与历史日志:
SELECT database, view, status, last_refresh_time, last_success_time, next_refresh_time, exception FROM system.view_refreshes WHERE database = 'robinhood' AND view = 'mv_token_daily_supply';
9. 常用分析查询示例
9.1 查询某代币的历史每日供应量演变
SELECT
day,
mint_count,
burn_count,
mint_amount,
burn_amount,
net_mint_amount,
cumulative_supply
FROM robinhood.token_daily_supply FINAL
WHERE token = unhex('5ebc7d0f2d01ed9ffe3f0617d30cb09664a17777')
ORDER BY day ASC;9.2 查询某天发行量(Mint)最大的前 10 个代币
SELECT
hex(token) AS token_address,
mint_count,
mint_amount,
burn_amount,
cumulative_supply
FROM robinhood.token_daily_supply FINAL
WHERE day = '2026-08-18'
ORDER BY mint_amount DESC
LIMIT 10;9.3 结合 tokens 元数据表换算人类可读供应量(若已安装 tokens 表)
SELECT
hex(s.token) AS token_address,
t.symbol,
s.latest_day,
s.cumulative_supply,
multiIf(
t.decimals = 18, toDecimal256(s.cumulative_supply, 18),
t.decimals = 6, toDecimal256(s.cumulative_supply, 6),
toDecimal256(s.cumulative_supply, 0)
) AS supply_formatted
FROM robinhood.v_token_latest_supply AS s
LEFT JOIN robinhood.tokens AS t FINAL ON t.address = s.token
WHERE s.cumulative_supply > 0
ORDER BY s.total_mint_count DESC
LIMIT 10;Token 元数据数据集(`tokens`)
{db}.tokens(DDL:sql/tokens.sql)缓存每个 ERC20/ERC721 token 地址的链上元数据:name / symbol / decimals / total_supply。这些字段不是从 logs 解码出来的(Transfer 事件里根本没有这些信息),而是由 chain…
Robinhood Chain 代币化股票(Stock Tokens)数据集
Robinhood Chain 的招牌资产:把美股/ETF 映射成链上 ERC-20 代币(TSLA、AAPL、 NVDA ……)。本文档记录这套代币是怎么部署/管理的(附证据:交易哈希、合约地 址)、识别规则和置信度、由此建出的表,以及几条可以直接抄的查询。