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交易对
池子-代币关系图谱示例 USDC ETH DAI WBTC 池子A ETH/USDC 0.3% 池子B ETH/DAI 0.05% 池子C USDC/WBTC 1% 代币 流动性池

这张图看起来简单,但实际应用中,当你面对几百个池子、上千个代币时,这种图谱能帮你快速发现「中心化代币」(出现在很多池子里的代币)和「孤立池子」(只交易一对代币的池子)。

4.6 映射数据的更新策略

链上数据是实时变化的。新池子不断被创建,旧池子可能被销毁。所以映射数据不能只做一次,需要持续更新。

我常用的策略是:

  1. 全量初始化:从创世块开始,扫描所有 PairCreated / PoolCreated 事件,建立初始映射表
  2. 增量更新:每隔一段时间(比如每100个区块),扫描新产生的事件,只添加新池子
  3. 定期校验:每周跑一次全量扫描,确保没有遗漏(比如某些DEX的工厂合约升级了)

效率提示:如果你用Python做链上数据抓取,建议用 web3.pyget_logs 方法,配合区块范围过滤,比逐块扫描快得多。

好了,这一章的内容就到这里。映射关系是链上数据分析的地基,地基打牢了,后面做流动性分析、交易量归因、套利路径挖掘,才能站得稳。


专注资料整理