# Holders API - 持有者统计服务 ## 📋 概述 这个模块提供了区块链代币持有者的统计和查询功能,包括: - **YT 代币持有者** (YT-A, YT-B, YT-C) - **ytLP 代币持有者** - **Lending 提供者** ## 🏗️ 架构 ``` ┌─────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Frontend │─────▶│ API Server │─────▶│ Database │ │ (Ant Design) │ │ (Gin + Gorm) │ │ (MySQL/PG) │ └─────────────────┘ └──────────────────┘ └──────────────┘ │ ▲ │ │ ▼ │ ┌──────────────────┐ │ │ Scanner Service │────────────┘ │ (Blockchain) │ └──────────────────┘ │ ▼ ┌──────────────────┐ │ Arbitrum RPC │ │ (Sepolia) │ └──────────────────┘ ``` ## 📁 文件结构 ``` holders/ ├── routers.go # API 路由和处理函数 ├── scanner.go # 区块链扫描器核心逻辑 └── README.md # 本文档 cmd/scanner/ └── main.go # 扫描器服务启动入口 models/ └── holder.go # 数据库模型定义 ``` ## 🔧 安装依赖 ### 1. 安装 Go 依赖 ```bash cd /home/coder/myprojects/assetx/golang-gin-realworld # 添加 ethereum 依赖 go get github.com/ethereum/go-ethereum go get github.com/ethereum/go-ethereum/ethclient go get github.com/ethereum/go-ethereum/accounts/abi go get github.com/ethereum/go-ethereum/common go get github.com/ethereum/go-ethereum/core/types go get github.com/ethereum/go-ethereum/crypto # 更新依赖 go mod tidy ``` ### 2. 数据库迁移 数据库表会在启动时自动创建(通过 GORM AutoMigrate): ```sql CREATE TABLE holder_snapshots ( id BIGSERIAL PRIMARY KEY, holder_address VARCHAR(42) NOT NULL, token_type VARCHAR(50) NOT NULL, token_address VARCHAR(42) NOT NULL, balance VARCHAR(78) NOT NULL, chain_id INTEGER NOT NULL, first_seen BIGINT NOT NULL, last_updated BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_holder_token (holder_address, token_type), INDEX idx_token_time (token_type, last_updated) ); ``` ## 🚀 启动服务 ### 方法一:分别启动(推荐开发环境) **1. 启动 API 服务器** ```bash cd /home/coder/myprojects/assetx/golang-gin-realworld # 设置环境变量 export DB_TYPE=mysql export DB_HOST=localhost export DB_PORT=3306 export DB_USER=root export DB_PASSWORD=your_password export DB_NAME=assetx export GIN_MODE=release export PORT=8080 # 启动 API 服务 go run main.go ``` **2. 启动扫描器服务** ```bash cd /home/coder/myprojects/assetx/golang-gin-realworld # 设置 RPC URL(可选,有默认值) export RPC_URL=https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07 # 启动扫描器 go run cmd/scanner/main.go ``` ### 方法二:使用 PM2(推荐生产环境) **1. 创建 PM2 配置文件** ```json // ecosystem.holders.config.js module.exports = { apps: [ { name: 'assetx-api', script: './golang-gin-realworld-example-app', env: { DB_TYPE: 'mysql', DB_HOST: 'localhost', DB_PORT: '3306', DB_USER: 'root', DB_PASSWORD: 'your_password', DB_NAME: 'assetx', GIN_MODE: 'release', PORT: '8080' } }, { name: 'holder-scanner', script: 'go', args: 'run cmd/scanner/main.go', env: { DB_TYPE: 'mysql', DB_HOST: 'localhost', DB_PORT: '3306', DB_USER: 'root', DB_PASSWORD: 'your_password', DB_NAME: 'assetx', RPC_URL: 'https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07' } } ] }; ``` **2. 启动服务** ```bash # 先编译 API 服务器 go build -o golang-gin-realworld-example-app # 使用 PM2 启动 pm2 start ecosystem.holders.config.js pm2 logs pm2 status ``` ## 📡 API 接口 ### 1. GET /api/holders/stats 获取所有代币类型的统计信息。 **请求** ```bash curl http://localhost:8080/api/holders/stats ``` **响应** ```json { "success": true, "data": [ { "token_type": "YT-A", "holder_count": 156, "total_balance": "1250000000000000000000" }, { "token_type": "YT-B", "holder_count": 89, "total_balance": "780000000000000000000" } ] } ``` ### 2. GET /api/holders/:tokenType 获取特定代币的持有者列表。 **参数** - `tokenType`: `YT-A` | `YT-B` | `YT-C` | `ytLP` | `Lending` **请求** ```bash curl http://localhost:8080/api/holders/YT-A ``` **响应** ```json { "success": true, "data": [ { "id": 1, "holder_address": "0x1234567890abcdef1234567890abcdef12345678", "token_type": "YT-A", "token_address": "0x97204190B35D9895a7a47aa7BaC61ac08De3cF05", "balance": "100000000000000000000", "chain_id": 421614, "first_seen": 1707800000, "last_updated": 1707900000 } ] } ``` ### 3. POST /api/holders/update 手动触发数据更新(需要管理员权限)。 **请求** ```bash curl -X POST http://localhost:8080/api/holders/update \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" ``` **响应** ```json { "success": true, "message": "Update triggered successfully", "timestamp": 1707900000 } ``` ## ⚙️ 配置说明 ### 扫描器配置 在 `cmd/scanner/main.go` 中可以修改以下配置: ```go scannerConfig := holders.Config{ RPCURL: "your_rpc_url", // RPC 节点地址 PollInterval: 10 * time.Second, // 轮询间隔(10秒) BatchSize: 9999, // 每批次查询的区块数 // 代币合约地址 YTVaults: []holders.VaultConfig{ {Name: "YT-A", Address: "0x..."}, {Name: "YT-B", Address: "0x..."}, {Name: "YT-C", Address: "0x..."}, }, YTLPAddress: "0x...", LendingAddress: "0x...", // 部署区块号(从这里开始扫描) DeploymentBlocks: holders.DeploymentBlocks{ YTVaults: 227339300, YTLP: 227230270, Lending: 227746053, }, } ``` ### 环境变量 | 变量名 | 说明 | 默认值 | |--------|------|--------| | `RPC_URL` | Arbitrum RPC 节点地址 | zan.top URL | | `DB_TYPE` | 数据库类型 | `sqlite` | | `DB_HOST` | 数据库主机 | `localhost` | | `DB_PORT` | 数据库端口 | `3306` | | `DB_USER` | 数据库用户名 | `root` | | `DB_PASSWORD` | 数据库密码 | - | | `DB_NAME` | 数据库名称 | `assetx` | ## 🔍 工作原理 ### 扫描流程 1. **初始扫描** - 从合约部署区块开始 - 扫描所有历史 Transfer/Supply 事件 - 记录所有曾经持有代币的地址 - 查询当前余额并保存到数据库 2. **增量扫描** - 每 10 秒检查一次新区块 - 只扫描自上次扫描后的新区块 - 更新新地址和余额变化 - 持续追踪地址首次出现时间 3. **数据存储** - 使用 `holder_snapshots` 表存储快照 - 余额以 wei 格式字符串存储(避免精度丢失) - 记录首次持有时间和最后更新时间 - 支持唯一约束(address + token_type) ### 性能优化 - ✅ 批量查询区块事件(每次最多 9999 个区块) - ✅ 增量扫描(只查询新区块) - ✅ 地址缓存(减少重复查询) - ✅ 并发控制(防止重复扫描) - ✅ 速率限制(避免 RPC 限流) ## 📊 监控和日志 ### 查看扫描器日志 ```bash # 实时查看日志 pm2 logs holder-scanner # 或者直接运行时查看 go run cmd/scanner/main.go ``` ### 日志示例 ``` === Holder Scanner Service === ✓ Configuration loaded ✓ Database tables checked 🚀 Starting blockchain scanner... === Holder Scanner Started === RPC: https://api.zan.top/node/v1/arb/sepolia/... Poll Interval: 10s 📊 Starting initial scan... Current block: 227950000 1. Scanning YT Vaults... Scanning YT-A (0x9720...)... Querying blocks 227339300 -> 227950000 (total: 610700 blocks) Querying blocks 227339300 - 227349299... ✓ Got 45 events ... Found 23 new addresses, total tracking: 156 YT-A: 156 holders saved 2. Scanning ytLP... ytLP: 67 holders saved 3. Scanning Lending... Lending: 43 holders saved 📌 Last scanned block: 227950000 ✓ Initial scan completed in 2m34s ⏰ Starting polling every 10s... ⏰ [15:30:45] No new blocks (current: 227950000) ⏰ [15:30:55] Found new blocks 🔄 Incremental scan: blocks 227950001 -> 227950015 ✓ Incremental scan completed in 1.2s ``` ## 🐛 故障排查 ### 常见问题 **1. RPC 连接失败** ``` Error: failed to connect to Ethereum client ``` **解决**: 检查 `RPC_URL` 是否正确,网络是否通畅 **2. 数据库连接失败** ``` Error: failed to connect to database ``` **解决**: 检查数据库配置和权限 **3. 查询超时** ``` Error: context deadline exceeded ``` **解决**: 减小 `BatchSize` 参数(如改为 5000) **4. 内存占用过高** ``` OOM or high memory usage ``` **解决**: 增加批次间隔时间或减小批次大小 ### 数据验证 **检查数据库中的数据** ```sql -- 查看所有代币类型的持有者数量 SELECT token_type, COUNT(*) as count FROM holder_snapshots GROUP BY token_type; -- 查看 YT-A 的前 10 名持有者 SELECT holder_address, balance FROM holder_snapshots WHERE token_type = 'YT-A' ORDER BY CAST(balance AS DECIMAL) DESC LIMIT 10; -- 查看最近更新的记录 SELECT * FROM holder_snapshots ORDER BY last_updated DESC LIMIT 10; ``` ## 🔐 安全注意事项 1. **RPC 密钥保护** - 不要在代码中硬编码 RPC URL - 使用环境变量或配置文件 - 限制 RPC API Key 的权限 2. **数据库安全** - 使用强密码 - 限制数据库访问权限 - 定期备份数据 3. **API 权限** - `/update` 接口需要管理员权限 - 建议添加请求频率限制 - 记录操作日志 ## 📝 TODO - [ ] 添加 WebSocket 支持(实时推送) - [ ] 实现手动触发扫描的实际逻辑 - [ ] 添加 Prometheus 监控指标 - [ ] 支持多链配置 - [ ] 添加数据导出功能 ## 📞 联系方式 如有问题请联系开发团队或查看项目文档。 --- **最后更新**: 2024-02-13