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:网络选择,比如 mainnetgoerlipolygon 等。
  • 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,先检查三件事:

  1. 合约地址对不对?大小写、前缀、末尾字符。
  2. 网络名称写对了没?别把 mainnet 写成 main-net
  3. 事件签名和 ABI 一致吗?尤其是参数类型和 indexed 关键字。

这三关过了,基本就不会出大问题。剩下的,就是写 mapping 逻辑了。咱们下一章见。