Ethers、Viem练习

水煮油条
Uniswap Pool 交易数据统计系统 - 需求文档
1. 项目概述
1.1 项目目标
构建一个 Uniswap V3 流动性池的交易数据统计系统,实时监控和分析 ARB/WETH 池的交易活动。
1.2 目标池信息
- 链: Arbitrum One
- 池地址:
0xC6962004f452bE9203591991D15f6b388e09E8D0

水煮油条
构建一个 Uniswap V3 流动性池的交易数据统计系统,实时监控和分析 ARB/WETH 池的交易活动。
0xC6962004f452bE9203591991D15f6b388e09E8D0Swap 事件Mint 事件(添加流动性)Burn 事件(移除流动性)Collect 事件(收集手续费)TEXT1src/collectors/ 2 ├── eventListener.ts # 事件监听器 3 ├── poolState.ts # 池状态读取 4 └── priceCalculator.ts # 价格计算
TEXT1src/processors/ 2 ├── swapProcessor.ts # Swap 事件处理 3 ├── liquidityProcessor.ts # 流动性事件处理 4 └── statsAggregator.ts # 统计聚合
TEXT1src/storage/ 2 ├── supabaseClient.ts # Supabase 客户端 3 ├── swapRepository.ts # 交易数据仓库 4 └── statsRepository.ts # 统计数据仓库
本项目将同时提供两种实现:
| 功能 | Ethers.js | Viem |
|---|---|---|
| 合约读取 | contract.slot0() | readContract() |
| 事件监听 | contract.on('Swap', ...) | watchContractEvent() |
| 批量调用 | 自定义 Multicall | 内置 multicall() |
| 性能 | 较慢 | 快 2-5 倍 |
| 类型安全 | 一般 | 强类型 |
SQL1-- ===================================================== 2-- Uniswap Pool 交易数据统计系统 - Database Schema 3-- Database: PostgreSQL (Supabase) 4-- Pool: ARB/WETH on Arbitrum 5-- ===================================================== 6 7-- 启用必要的扩展 8CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 9CREATE EXTENSION IF NOT EXISTS "pg_stat_statements"; 10 11-- ===================================================== 12-- 1. 交易记录表 (Swap Events) 13-- ===================================================== 14CREATE TABLE IF NOT EXISTS swaps ( 15 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 16 17 -- 区块链基础信息 18 transaction_hash VARCHAR(66) NOT NULL, 19 block_number BIGINT NOT NULL, 20 block_timestamp TIMESTAMP WITH TIME ZONE NOT NULL, 21 log_index INTEGER NOT NULL, 22 23 -- 交易参与方 24 sender VARCHAR(42) NOT NULL, 25 recipient VARCHAR(42) NOT NULL, 26 27 -- 交易数据 28 amount0 DECIMAL(78, 0) NOT NULL, -- token0 数量变化(wei) 29 amount1 DECIMAL(78, 0) NOT NULL, -- token1 数量变化(wei) 30 sqrt_price_x96 DECIMAL(78, 0) NOT NULL, -- 价格 (Q64.96 格式) 31 liquidity DECIMAL(78, 0) NOT NULL, 32 tick INTEGER NOT NULL, 33 34 -- 计算字段 35 amount0_readable DECIMAL(28, 18), -- token0 可读数量 36 amount1_readable DECIMAL(28, 18), -- token1 可读数量 37 price_token0 DECIMAL(28, 18), -- token0 价格(以 token1 计价) 38 price_token1 DECIMAL(28, 18), -- token1 价格(以 token0 计价) 39 swap_type VARCHAR(4) NOT NULL CHECK (swap_type IN ('BUY', 'SELL')), -- 买入/卖出 ARB 40 usd_value DECIMAL(18, 2), -- 交易价值(USD) 41 42 -- Gas 信息 43 gas_used BIGINT, 44 gas_price DECIMAL(28, 0), -- wei 45 transaction_fee DECIMAL(28, 18), -- ETH 46 47 -- 元数据 48 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 49 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 50 51 -- 唯一约束 52 UNIQUE(transaction_hash, log_index) 53); 54 55-- 索引优化 56CREATE INDEX idx_swaps_block_number ON swaps(block_number DESC); 57CREATE INDEX idx_swaps_block_timestamp ON swaps(block_timestamp DESC); 58CREATE INDEX idx_swaps_sender ON swaps(sender); 59CREATE INDEX idx_swaps_recipient ON swaps(recipient); 60CREATE INDEX idx_swaps_swap_type ON swaps(swap_type); 61CREATE INDEX idx_swaps_usd_value ON swaps(usd_value DESC) WHERE usd_value IS NOT NULL; 62CREATE INDEX idx_swaps_composite ON swaps(block_timestamp DESC, swap_type); 63 64-- 分区表(按月分区,提高大数据查询性能) 65-- CREATE TABLE swaps_2024_01 PARTITION OF swaps 66-- FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); 67 68COMMENT ON TABLE swaps IS 'Uniswap V3 Swap 事件记录'; 69COMMENT ON COLUMN swaps.amount0 IS '正数表示流入池子,负数表示流出池子'; 70COMMENT ON COLUMN swaps.swap_type IS 'BUY=买入ARB,SELL=卖出ARB'; 71 72-- ===================================================== 73-- 2. 流动性事件表 (Mint/Burn Events) 74-- ===================================================== 75CREATE TABLE IF NOT EXISTS liquidity_events ( 76 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 77 78 -- 区块链基础信息 79 transaction_hash VARCHAR(66) NOT NULL, 80 block_number BIGINT NOT NULL, 81 block_timestamp TIMESTAMP WITH TIME ZONE NOT NULL, 82 log_index INTEGER NOT NULL, 83 84 -- 事件类型 85 event_type VARCHAR(10) NOT NULL CHECK (event_type IN ('MINT', 'BURN', 'COLLECT')), 86 87 -- 用户信息 88 owner VARCHAR(42) NOT NULL, 89 sender VARCHAR(42), 90 91 -- 流动性信息 92 liquidity_delta DECIMAL(78, 0) NOT NULL, -- 流动性变化 93 tick_lower INTEGER NOT NULL, 94 tick_upper INTEGER NOT NULL, 95 96 -- Token 数量 97 amount0 DECIMAL(78, 0) NOT NULL, 98 amount1 DECIMAL(78, 0) NOT NULL, 99 amount0_readable DECIMAL(28, 18), 100 amount1_readable DECIMAL(28, 18), 101 usd_value DECIMAL(18, 2), 102 103 -- 元数据 104 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 105 106 UNIQUE(transaction_hash, log_index) 107); 108 109CREATE INDEX idx_liquidity_events_block_timestamp ON liquidity_events(block_timestamp DESC); 110CREATE INDEX idx_liquidity_events_owner ON liquidity_events(owner); 111CREATE INDEX idx_liquidity_events_event_type ON liquidity_events(event_type); 112CREATE INDEX idx_liquidity_events_tick_range ON liquidity_events(tick_lower, tick_upper); 113 114COMMENT ON TABLE liquidity_events IS '流动性变化事件(添加/移除/收集)'; 115 116-- ===================================================== 117-- 3. Pool 状态快照表 118-- ===================================================== 119CREATE TABLE IF NOT EXISTS pool_snapshots ( 120 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 121 122 -- 快照时间 123 snapshot_time TIMESTAMP WITH TIME ZONE NOT NULL, 124 block_number BIGINT NOT NULL, 125 126 -- Pool 状态 127 sqrt_price_x96 DECIMAL(78, 0) NOT NULL, 128 tick INTEGER NOT NULL, 129 liquidity DECIMAL(78, 0) NOT NULL, 130 131 -- 计算字段 132 price_token0 DECIMAL(28, 18), 133 price_token1 DECIMAL(28, 18), 134 tvl_usd DECIMAL(18, 2), -- Total Value Locked 135 136 -- Token 余额 137 token0_balance DECIMAL(28, 18), 138 token1_balance DECIMAL(28, 18), 139 140 -- 24h 统计 141 volume_24h_usd DECIMAL(18, 2), 142 fees_24h_usd DECIMAL(18, 2), 143 transactions_24h INTEGER, 144 145 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 146 147 UNIQUE(snapshot_time) 148); 149 150CREATE INDEX idx_pool_snapshots_time ON pool_snapshots(snapshot_time DESC); 151CREATE INDEX idx_pool_snapshots_block ON pool_snapshots(block_number DESC); 152 153COMMENT ON TABLE pool_snapshots IS '每小时 Pool 状态快照'; 154 155-- ===================================================== 156-- 4. 小时统计表 (OHLC) 157-- ===================================================== 158CREATE TABLE IF NOT EXISTS hourly_stats ( 159 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 160 161 -- 时间范围 162 hour_start TIMESTAMP WITH TIME ZONE NOT NULL, 163 hour_end TIMESTAMP WITH TIME ZONE NOT NULL, 164 165 -- OHLC 数据 166 open_price DECIMAL(28, 18) NOT NULL, 167 high_price DECIMAL(28, 18) NOT NULL, 168 low_price DECIMAL(28, 18) NOT NULL, 169 close_price DECIMAL(28, 18) NOT NULL, 170 171 -- 交易统计 172 total_transactions INTEGER NOT NULL DEFAULT 0, 173 buy_transactions INTEGER NOT NULL DEFAULT 0, 174 sell_transactions INTEGER NOT NULL DEFAULT 0, 175 176 -- 交易量 177 volume_token0 DECIMAL(28, 18) NOT NULL DEFAULT 0, 178 volume_token1 DECIMAL(28, 18) NOT NULL DEFAULT 0, 179 volume_usd DECIMAL(18, 2) NOT NULL DEFAULT 0, 180 181 -- 手续费 182 fees_token0 DECIMAL(28, 18) NOT NULL DEFAULT 0, 183 fees_token1 DECIMAL(28, 18) NOT NULL DEFAULT 0, 184 fees_usd DECIMAL(18, 2) NOT NULL DEFAULT 0, 185 186 -- 用户统计 187 unique_addresses INTEGER NOT NULL DEFAULT 0, 188 unique_senders INTEGER NOT NULL DEFAULT 0, 189 190 -- 流动性 191 avg_liquidity DECIMAL(78, 0), 192 min_liquidity DECIMAL(78, 0), 193 max_liquidity DECIMAL(78, 0), 194 195 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 196 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 197 198 UNIQUE(hour_start) 199); 200 201CREATE INDEX idx_hourly_stats_time ON hourly_stats(hour_start DESC); 202 203COMMENT ON TABLE hourly_stats IS '每小时交易统计和 OHLC 数据'; 204 205-- ===================================================== 206-- 5. 日统计表 207-- ===================================================== 208CREATE TABLE IF NOT EXISTS daily_stats ( 209 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 210 211 -- 日期 212 date DATE NOT NULL, 213 214 -- OHLC 数据 215 open_price DECIMAL(28, 18) NOT NULL, 216 high_price DECIMAL(28, 18) NOT NULL, 217 low_price DECIMAL(28, 18) NOT NULL, 218 close_price DECIMAL(28, 18) NOT NULL, 219 220 -- 交易统计 221 total_transactions INTEGER NOT NULL DEFAULT 0, 222 buy_transactions INTEGER NOT NULL DEFAULT 0, 223 sell_transactions INTEGER NOT NULL DEFAULT 0, 224 225 -- 交易量 226 volume_token0 DECIMAL(28, 18) NOT NULL DEFAULT 0, 227 volume_token1 DECIMAL(28, 18) NOT NULL DEFAULT 0, 228 volume_usd DECIMAL(18, 2) NOT NULL DEFAULT 0, 229 230 -- 手续费 231 fees_token0 DECIMAL(28, 18) NOT NULL DEFAULT 0, 232 fees_token1 DECIMAL(28, 18) NOT NULL DEFAULT 0, 233 fees_usd DECIMAL(18, 2) NOT NULL DEFAULT 0, 234 235 -- 用户统计 236 unique_addresses INTEGER NOT NULL DEFAULT 0, 237 new_addresses INTEGER NOT NULL DEFAULT 0, 238 239 -- 流动性 240 avg_tvl_usd DECIMAL(18, 2), 241 end_tvl_usd DECIMAL(18, 2), 242 243 -- 大额交易 244 whale_transactions INTEGER NOT NULL DEFAULT 0, -- > 10,000 USD 245 largest_transaction_usd DECIMAL(18, 2), 246 247 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 248 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 249 250 UNIQUE(date) 251); 252 253CREATE INDEX idx_daily_stats_date ON daily_stats(date DESC); 254 255COMMENT ON TABLE daily_stats IS '每日交易统计汇总'; 256 257-- ===================================================== 258-- 6. 用户统计表 259-- ===================================================== 260CREATE TABLE IF NOT EXISTS user_stats ( 261 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 262 263 -- 用户地址 264 address VARCHAR(42) NOT NULL, 265 266 -- 交易统计 267 total_transactions INTEGER NOT NULL DEFAULT 0, 268 buy_transactions INTEGER NOT NULL DEFAULT 0, 269 sell_transactions INTEGER NOT NULL DEFAULT 0, 270 271 -- 交易量 272 total_volume_usd DECIMAL(18, 2) NOT NULL DEFAULT 0, 273 largest_transaction_usd DECIMAL(18, 2), 274 275 -- 时间信息 276 first_transaction_at TIMESTAMP WITH TIME ZONE, 277 last_transaction_at TIMESTAMP WITH TIME ZONE, 278 279 -- 流动性提供 280 is_liquidity_provider BOOLEAN DEFAULT FALSE, 281 total_liquidity_provided_usd DECIMAL(18, 2) DEFAULT 0, 282 283 -- 标签 284 user_type VARCHAR(20) CHECK (user_type IN ('RETAIL', 'WHALE', 'BOT', 'LP', 'MEV')), 285 286 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 287 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 288 289 UNIQUE(address) 290); 291 292CREATE INDEX idx_user_stats_address ON user_stats(address); 293CREATE INDEX idx_user_stats_volume ON user_stats(total_volume_usd DESC); 294CREATE INDEX idx_user_stats_type ON user_stats(user_type); 295 296COMMENT ON TABLE user_stats IS '用户交易行为统计'; 297 298-- ===================================================== 299-- 7. 价格历史表(用于图表) 300-- ===================================================== 301CREATE TABLE IF NOT EXISTS price_history ( 302 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 303 304 timestamp TIMESTAMP WITH TIME ZONE NOT NULL, 305 block_number BIGINT NOT NULL, 306 307 price DECIMAL(28, 18) NOT NULL, 308 309 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 310 311 UNIQUE(timestamp) 312); 313 314CREATE INDEX idx_price_history_timestamp ON price_history(timestamp DESC); 315 316COMMENT ON TABLE price_history IS '价格历史记录(每笔交易后)'; 317 318-- ===================================================== 319-- 8. 系统监控表 320-- ===================================================== 321CREATE TABLE IF NOT EXISTS sync_status ( 322 id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 323 324 -- 同步状态 325 last_synced_block BIGINT NOT NULL, 326 last_synced_timestamp TIMESTAMP WITH TIME ZONE NOT NULL, 327 current_block BIGINT, 328 329 -- 统计 330 total_events_synced BIGINT NOT NULL DEFAULT 0, 331 sync_errors INTEGER NOT NULL DEFAULT 0, 332 last_error TEXT, 333 last_error_at TIMESTAMP WITH TIME ZONE, 334 335 -- 性能 336 avg_sync_time_ms INTEGER, 337 338 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() 339); 340 341-- 初始化同步状态 342INSERT INTO sync_status (last_synced_block, last_synced_timestamp, total_events_synced) 343VALUES (0, NOW() - INTERVAL '30 days', 0) 344ON CONFLICT DO NOTHING; 345 346COMMENT ON TABLE sync_status IS '数据同步状态监控'; 347 348-- ===================================================== 349-- 9. 触发器 - 自动更新 updated_at 350-- ===================================================== 351CREATE OR REPLACE FUNCTION update_updated_at_column() 352RETURNS TRIGGER AS $$ 353BEGIN 354 NEW.updated_at = NOW(); 355 RETURN NEW; 356END; 357$$ LANGUAGE plpgsql; 358 359CREATE TRIGGER update_swaps_updated_at 360 BEFORE UPDATE ON swaps 361 FOR EACH ROW 362 EXECUTE FUNCTION update_updated_at_column(); 363 364CREATE TRIGGER update_hourly_stats_updated_at 365 BEFORE UPDATE ON hourly_stats 366 FOR EACH ROW 367 EXECUTE FUNCTION update_updated_at_column(); 368 369CREATE TRIGGER update_daily_stats_updated_at 370 BEFORE UPDATE ON daily_stats 371 FOR EACH ROW 372 EXECUTE FUNCTION update_updated_at_column(); 373 374CREATE TRIGGER update_user_stats_updated_at 375 BEFORE UPDATE ON user_stats 376 FOR EACH ROW 377 EXECUTE FUNCTION update_updated_at_column(); 378 379-- ===================================================== 380-- 10. 视图 - 常用查询 381-- ===================================================== 382 383-- 最近 24 小时交易统计 384CREATE OR REPLACE VIEW v_stats_24h AS 385SELECT 386 COUNT(*) as total_transactions, 387 COUNT(*) FILTER (WHERE swap_type = 'BUY') as buy_transactions, 388 COUNT(*) FILTER (WHERE swap_type = 'SELL') as sell_transactions, 389 SUM(usd_value) as total_volume_usd, 390 AVG(usd_value) as avg_transaction_size_usd, 391 MAX(usd_value) as largest_transaction_usd, 392 COUNT(DISTINCT sender) as unique_traders, 393 SUM(usd_value) * 0.0005 as estimated_fees_usd -- 0.05% fee 394FROM swaps 395WHERE block_timestamp > NOW() - INTERVAL '24 hours'; 396 397-- 最近 7 天每日统计 398CREATE OR REPLACE VIEW v_stats_7d AS 399SELECT 400 DATE(block_timestamp) as date, 401 COUNT(*) as total_transactions, 402 SUM(usd_value) as volume_usd, 403 COUNT(DISTINCT sender) as unique_traders, 404 MIN(price_token0) as low_price, 405 MAX(price_token0) as high_price 406FROM swaps 407WHERE block_timestamp > NOW() - INTERVAL '7 days' 408GROUP BY DATE(block_timestamp) 409ORDER BY date DESC; 410 411-- 大额交易(Whale Alert) 412CREATE OR REPLACE VIEW v_whale_transactions AS 413SELECT 414 transaction_hash, 415 block_timestamp, 416 sender, 417 swap_type, 418 usd_value, 419 amount0_readable, 420 amount1_readable 421FROM swaps 422WHERE usd_value > 10000 423ORDER BY block_timestamp DESC; 424 425-- Top 交易者 426CREATE OR REPLACE VIEW v_top_traders AS 427SELECT 428 address, 429 total_transactions, 430 total_volume_usd, 431 user_type, 432 last_transaction_at 433FROM user_stats 434ORDER BY total_volume_usd DESC 435LIMIT 100; 436 437-- ===================================================== 438-- 11. RLS (Row Level Security) 策略 439-- ===================================================== 440-- 如果需要多租户或权限控制,可以启用 RLS 441 442-- ALTER TABLE swaps ENABLE ROW LEVEL SECURITY; 443-- CREATE POLICY "Public read access" ON swaps FOR SELECT USING (true); 444 445-- ===================================================== 446-- 12. 性能优化 - 定期维护脚本 447-- ===================================================== 448 449-- 定期 VACUUM 和 ANALYZE 450-- 可以通过 pg_cron 扩展或外部 cron job 执行 451-- VACUUM ANALYZE swaps; 452-- VACUUM ANALYZE liquidity_events; 453 454-- 清理旧数据(可选,保留最近 1 年数据) 455-- DELETE FROM swaps WHERE block_timestamp < NOW() - INTERVAL '1 year'; 456 457-- ===================================================== 458-- 13. 初始数据和测试 459-- ===================================================== 460 461-- 插入测试数据(开发环境) 462-- INSERT INTO swaps ( 463-- transaction_hash, block_number, block_timestamp, log_index, 464-- sender, recipient, amount0, amount1, sqrt_price_x96, liquidity, tick, 465-- amount0_readable, amount1_readable, price_token0, swap_type, usd_value 466-- ) VALUES ( 467-- '0x1234...', 150000000, NOW(), 0, 468-- '0xabc...', '0xdef...', -1000000000000000000, 500000000000000000, 469-- 79228162514264337593543950336, 1000000000000000000, 100000, 470-- -1.0, 0.5, 0.5, 'SELL', 1000.00 471-- ); 472 473-- ===================================================== 474-- 完成 475-- ===================================================== 476 477-- 检查所有表 478SELECT table_name 479FROM information_schema.tables 480WHERE table_schema = 'public' 481ORDER BY table_name; 482 483-- 检查索引 484SELECT 485 tablename, 486 indexname, 487 indexdef 488FROM pg_indexes 489WHERE schemaname = 'public' 490ORDER BY tablename, indexname; 491