4、子图清单(subgraph.yaml):数据源配置、合约地址、网络选择、事件监听
好,咱们今天聊聊子图清单,也就是 subgraph.yaml 这个文件。
说实话,很多刚接触 The Graph 的朋友,上来就写 mapping 逻辑,结果折腾半天发现数据没进来。为什么?八成是 yaml 配错了。我个人习惯是,先花 10 分钟把 yaml 理清楚,后面能省下大把调试时间。
4.1 数据源配置:告诉子图去哪儿找数据
数据源,说白了就是告诉子图:「嘿,你要监听哪个合约?在哪个链上?事件长什么样?」
一个典型的 subgraph.yaml 长这样:
specVersion: 0.0.5
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: MyContract
network: mainnet
source:
address: "0x1234567890abcdef1234567890abcdef12345678"
abi: MyContract
startBlock: 15000000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
- Approval
abis:
- name: MyContract
file: ./abis/MyContract.json
eventHandlers:
- event: Transfer(address indexed from, address indexed to, uint256 value)
handler: handleTransfer
- event: Approval(address indexed owner, address indexed spender, uint256 value)
handler: handleApproval
file: ./src/mapping.ts
嗯,这里要注意几个关键点。
核心字段解析:
kind:数据源类型。目前主流是ethereum,未来可能支持更多链。name:你自己起的名字,方便在代码里引用。network:网络选择,比如mainnet、goerli、polygon等。source:合约地址、ABI 文件、起始区块号。mapping:事件处理逻辑的配置。
4.2 合约地址:别写错了,真的会哭
合约地址这块,我踩过坑。有一次我把地址复制过来,结果末尾少了一位。部署上去后,子图一直显示「syncing 0%」。查了半天,才发现地址不对。
避坑指南:
- 地址必须是小写。虽然以太坊地址不区分大小写,但 The Graph 的解析器对大小写敏感。
- 不要带
0x以外的前缀。有人从 Etherscan 复制时,不小心把0x后面的空格也带上了。 - 起始区块号
startBlock建议填合约部署的区块。填太早,子图会扫描大量无用区块;填太晚,可能漏掉早期事件。
我个人习惯是,先在 Etherscan 上找到合约的「Contract Creation」交易,记下那个区块号。然后 startBlock 就填那个值,或者往前推 100 个区块,确保万无一失。
4.3 网络选择:主网还是测试网?
网络选择其实很简单,但新手容易搞混。你想想看,mainnet 对应以太坊主网,goerli 对应 Goerli 测试网,matic 对应 Polygon 主网。
我建议在开发阶段,先用测试网。为什么呢?因为测试网上的交易免费,而且区块少,同步快。我曾经在 Goerli 上调试一个子图,从部署到同步完成,只用了 3 分钟。要是主网,至少半小时起步。
| 网络名称 | 对应链 | 适用场景 |
|---|---|---|
mainnet |
以太坊主网 | 生产环境 |
goerli |
Goerli 测试网 | 开发调试 |
matic |
Polygon 主网 | Polygon 生态 |
mumbai |
Polygon 测试网 | Polygon 开发 |
小技巧:如果你不确定网络名称怎么写,可以去 The Graph 官方文档的「Supported Networks」页面查一下。每个网络都有对应的 network 字符串,别自己瞎猜。
4.4 事件监听:核心中的核心
事件监听这部分,说白了就是告诉子图:「当合约触发某个事件时,你该做什么。」
每个 eventHandler 包含两部分:
event:事件签名。必须和合约 ABI 里定义的一模一样。handler:处理函数名。对应 mapping 文件里的函数。
举个例子,假设你的合约有个 Transfer 事件:
event Transfer(address indexed from, address indexed to, uint256 value)
那么在 yaml 里就要写成:
eventHandlers:
- event: Transfer(address indexed from, address indexed to, uint256 value)
handler: handleTransfer
注意,事件签名里的参数名可以省略,但类型和 indexed 关键字必须保留。我见过有人把 indexed 漏了,结果子图能编译,但事件永远触发不了。
我曾经遇到过一个坑:某个合约的事件参数里有个 bytes32 类型,我在 yaml 里写成了 string。结果子图同步到那个事件时直接崩溃。后来查了半天,才发现是类型不匹配。所以,事件签名一定要和 ABI 完全一致,一个字符都不能差。
4.5 多数据源配置:一个子图监听多个合约
有时候,一个子图需要监听多个合约。比如 Uniswap 的子图,既要监听 Factory 合约,又要监听 Pair 合约。
这时候,你可以在 dataSources 数组里添加多个数据源:
dataSources:
- kind: ethereum
name: Factory
network: mainnet
source:
address: "0x..."
abi: Factory
startBlock: 10000000
mapping:
...
- kind: ethereum
name: Pair
network: mainnet
source:
address: "0x..."
abi: Pair
startBlock: 10000000
mapping:
...
每个数据源独立配置自己的合约地址、ABI 和事件处理器。这样,一个子图就能同时处理多个合约的数据了。
个人建议:如果多个合约共享同一个 ABI,可以把 ABI 文件单独放一个目录,然后在 abis 字段里引用同一个文件。别每个数据源都复制一份 ABI,那样维护起来太痛苦了。
4.6 总结一下
嗯,subgraph.yaml 其实就是子图的「地图」。它告诉子图去哪儿找数据、怎么处理数据。配置好了,后面就顺风顺水;配错了,调试起来真要命。
我个人经验是,每次写完 yaml,先检查三件事:
- 合约地址对不对?大小写、前缀、末尾字符。
- 网络名称写对了没?别把
mainnet写成main-net。 - 事件签名和 ABI 一致吗?尤其是参数类型和
indexed关键字。
这三关过了,基本就不会出大问题。剩下的,就是写 mapping 逻辑了。咱们下一章见。