4、交易对与流动性池映射:通过合约地址关联交易对,构建池子-代币关系图谱
好,咱们继续往下走。前面几章我们把交易数据的基本字段、清洗方法都捋了一遍。但说实话,光有这些零散的交易记录,你根本看不清全局。就像你手里有一堆拼图碎片,但不知道它们属于哪幅画。
这一章,我们要解决的核心问题是:如何把交易对和流动性池子精准地关联起来。说白了,就是给你一个合约地址,你得能立刻说出它对应的是哪个交易对,池子里有哪些代币,流动性深度怎么样。
我个人习惯把这一步叫做「链上数据的骨架搭建」。骨架搭好了,后面做分析才不会跑偏。
4.1 为什么需要映射?—— 从交易记录到池子关系
你想想看,一条DEX交易记录里,通常只告诉你「谁在哪个合约地址交易了」。但那个合约地址到底代表什么?是ETH/USDC池,还是某个土狗币的池子?
我刚开始做链上分析时,就踩过这个坑。有一次我拉了一大堆Uniswap V3的交易数据,直接按交易量排序,结果排第一的居然是个刚部署3小时的垃圾币池子。为什么?因为我没做池子-代币映射,把不同池子的交易量混在一起了。
所以,映射的核心目的就两个:
- 识别池子身份:知道这个合约地址对应哪个交易对
- 构建关系图谱:搞清楚代币A、代币B、池子C三者之间的关联
核心公式:
交易对 = (代币A, 代币B, 手续费层级) → 流动性池合约地址
4.2 映射的底层逻辑 —— 从合约事件中提取信息
嗯,这里要注意。DEX的池子合约地址是固定的,但交易对信息并不会直接写在合约里。你得从链上事件(Event)中提取。
以Uniswap V2为例,池子部署时会触发一个 PairCreated 事件。这个事件里包含了:
token0:代币A的合约地址token1:代币B的合约地址pair:池子合约地址allPairsLength:当前池子总数
我一般会这样解析:
// 伪代码示例
event PairCreated(
address indexed token0,
address indexed token1,
address pair,
uint256
);
// 解析逻辑
function mapPairToTokens(pairAddress) {
// 从事件日志中提取
token0 = event.token0;
token1 = event.token1;
// 构建映射关系
pairMap[pairAddress] = {
token0: token0,
token1: token1,
pairLabel: token0.symbol + "/" + token1.symbol
};
}
对于Uniswap V3,稍微复杂一点。它的事件是 PoolCreated,多了一个 fee 字段(手续费层级)。同一个代币对,可能有多个不同手续费的池子。
小技巧:我建议你在映射时,把手续费也作为池子标识的一部分。比如 ETH/USDC 0.05% 和 ETH/USDC 0.3% 是两个不同的池子,不能混为一谈。
4.3 构建池子-代币关系图谱
有了映射关系,我们就可以画一张图了。这张图能帮你快速回答:
- 某个代币在哪些池子里交易?
- 哪些池子共享同一个代币?
- 流动性主要集中在哪个池子?
我习惯用图数据库(比如Neo4j)来存这种关系,但如果你只是做数据分析,用关系型数据库也够用。下面是一个简单的表结构:
| 字段名 | 类型 | 说明 |
|---|---|---|
| pool_address | VARCHAR(42) | 池子合约地址 |
| token0_address | VARCHAR(42) | 代币A地址 |
| token1_address | VARCHAR(42) | 代币B地址 |
| fee_tier | INT | 手续费层级(V3特有) |
| dex_name | VARCHAR(20) | 所属DEX(Uniswap/SushiSwap等) |
| created_block | BIGINT | 创建时的区块号 |
有了这张表,你就能轻松写出类似这样的查询:
-- 查询某个代币参与的所有池子
SELECT pool_address, token0_address, token1_address
FROM pool_mapping
WHERE token0_address = '0x...'
OR token1_address = '0x...';
-- 查询某个交易对的所有池子(含不同手续费)
SELECT pool_address, fee_tier
FROM pool_mapping
WHERE (token0_address = '0xA' AND token1_address = '0xB')
OR (token0_address = '0xB' AND token1_address = '0xA');
4.4 实战中的坑与避坑指南
我曾经在映射SushiSwap的池子时,发现同一个交易对居然出现了两个不同的池子地址。排查了半天,才发现是SushiSwap在迁移时,旧池子没有被销毁,新池子又部署了一个。所以,一定要加上DEX名称作为过滤条件。
另外,还有一个容易忽略的点:代币地址的大小写。以太坊地址虽然不区分大小写,但有些数据库在存储时会把地址转成小写。如果你查询时用了大写,可能就匹配不上。我建议统一用小写存储。
警告:不要直接使用代币的symbol(如USDC)作为映射键。因为不同链、不同项目可能使用相同的symbol。一定要用合约地址作为唯一标识。
4.5 可视化:池子-代币关系图谱
下面我用SVG画了一张关系图,展示三个池子与四个代币之间的映射关系。你可以直观地看到:
- USDC 同时出现在池子A和池子C中
- ETH 只出现在池子A和池子B中
- 池子B是ETH/DAI交易对
这张图看起来简单,但实际应用中,当你面对几百个池子、上千个代币时,这种图谱能帮你快速发现「中心化代币」(出现在很多池子里的代币)和「孤立池子」(只交易一对代币的池子)。
4.6 映射数据的更新策略
链上数据是实时变化的。新池子不断被创建,旧池子可能被销毁。所以映射数据不能只做一次,需要持续更新。
我常用的策略是:
- 全量初始化:从创世块开始,扫描所有
PairCreated/PoolCreated事件,建立初始映射表 - 增量更新:每隔一段时间(比如每100个区块),扫描新产生的事件,只添加新池子
- 定期校验:每周跑一次全量扫描,确保没有遗漏(比如某些DEX的工厂合约升级了)
效率提示:如果你用Python做链上数据抓取,建议用 web3.py 的 get_logs 方法,配合区块范围过滤,比逐块扫描快得多。
好了,这一章的内容就到这里。映射关系是链上数据分析的地基,地基打牢了,后面做流动性分析、交易量归因、套利路径挖掘,才能站得稳。