子图索引技能 subgraph-indexing

子图索引技能是用于The Graph协议的专业开发能力,专注于区块链数据索引和查询。该技能涵盖子图清单配置、GraphQL模式设计、AssemblyScript事件处理器开发、实体关系管理、本地测试以及部署到托管和去中心化网络。关键词:区块链索引、The Graph、子图开发、Web3数据查询、智能合约事件处理、去中心化数据API、GraphQL模式、AssemblyScript、数据索引优化、链上数据聚合。

DApp开发 0 次安装 0 次浏览 更新于 2/23/2026

名称: 子图索引 描述: The Graph协议的子图开发。包括清单配置、GraphQL模式设计、AssemblyScript处理器、实体关系以及部署到托管和去中心化网络。 允许工具: 读取, 搜索, 写入, Bash, 编辑, 全局搜索, 网络获取

子图索引技能

The Graph协议的子图开发,实现高效的区块链数据索引和查询。

能力

  • 清单配置: 编写subgraph.yaml文件
  • 模式设计: 定义实体的GraphQL模式
  • 事件处理器: 实现AssemblyScript处理器
  • 实体关系: 处理派生字段和关系
  • 本地测试: 使用本地Graph节点测试
  • 部署: 部署到托管和去中心化网络
  • 性能: 优化索引性能
  • 重组处理: 处理链重组

安装

# 安装Graph CLI
npm install -g @graphprotocol/graph-cli

# 验证
graph --version

项目设置

初始化子图

# 从现有合约初始化
graph init --product subgraph-studio \
  --from-contract 0x... \
  --network mainnet \
  --abi ./abi.json \
  my-subgraph

cd my-subgraph

项目结构

my-subgraph/
├── subgraph.yaml          # 清单
├── schema.graphql         # GraphQL模式
├── src/
│   └── mapping.ts         # 事件处理器
├── abis/
│   └── Contract.json      # 合约ABI
├── tests/
│   └── contract.test.ts   # 单元测试
└── package.json

清单 (subgraph.yaml)

specVersion: 0.0.5
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum
    name: Token
    network: mainnet
    source:
      address: "0x..."
      abi: Token
      startBlock: 18000000
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Transfer
        - Account
      abis:
        - name: Token
          file: ./abis/Token.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
        - event: Approval(indexed address,indexed address,uint256)
          handler: handleApproval
      callHandlers:
        - function: transfer(address,uint256)
          handler: handleTransferCall
      blockHandlers:
        - handler: handleBlock
      file: ./src/mapping.ts

GraphQL模式

# schema.graphql

type Token @entity {
  id: ID!
  name: String!
  symbol: String!
  decimals: Int!
  totalSupply: BigInt!
  holders: [Account!]! @derivedFrom(field: "token")
}

type Account @entity {
  id: ID!
  token: Token!
  balance: BigInt!
  transfersFrom: [Transfer!]! @derivedFrom(field: "from")
  transfersTo: [Transfer!]! @derivedFrom(field: "to")
}

type Transfer @entity {
  id: ID!
  from: Account!
  to: Account!
  value: BigInt!
  timestamp: BigInt!
  blockNumber: BigInt!
  transactionHash: Bytes!
}

# 用于聚合
type DailyVolume @entity {
  id: ID!  # 日期字符串
  date: BigInt!
  volume: BigInt!
  txCount: Int!
}

AssemblyScript处理器

// src/mapping.ts
import { Transfer as TransferEvent } from "../generated/Token/Token";
import { Token, Account, Transfer, DailyVolume } from "../generated/schema";
import { BigInt, Bytes, Address } from "@graphprotocol/graph-ts";

export function handleTransfer(event: TransferEvent): void {
  // 创建Transfer实体
  let transfer = new Transfer(
    event.transaction.hash.toHex() + "-" + event.logIndex.toString()
  );
  transfer.from = getOrCreateAccount(event.params.from).id;
  transfer.to = getOrCreateAccount(event.params.to).id;
  transfer.value = event.params.value;
  transfer.timestamp = event.block.timestamp;
  transfer.blockNumber = event.block.number;
  transfer.transactionHash = event.transaction.hash;
  transfer.save();

  // 更新账户余额
  updateAccountBalance(event.params.from, event.params.value.neg());
  updateAccountBalance(event.params.to, event.params.value);

  // 更新日交易量
  updateDailyVolume(event.block.timestamp, event.params.value);
}

function getOrCreateAccount(address: Address): Account {
  let id = address.toHex();
  let account = Account.load(id);

  if (account == null) {
    account = new Account(id);
    account.token = "token-id";
    account.balance = BigInt.fromI32(0);
    account.save();
  }

  return account;
}

function updateAccountBalance(address: Address, delta: BigInt): void {
  let account = getOrCreateAccount(address);
  account.balance = account.balance.plus(delta);
  account.save();
}

function updateDailyVolume(timestamp: BigInt, value: BigInt): void {
  let dayId = timestamp.toI32() / 86400;
  let id = dayId.toString();

  let volume = DailyVolume.load(id);
  if (volume == null) {
    volume = new DailyVolume(id);
    volume.date = BigInt.fromI32(dayId * 86400);
    volume.volume = BigInt.fromI32(0);
    volume.txCount = 0;
  }

  volume.volume = volume.volume.plus(value);
  volume.txCount = volume.txCount + 1;
  volume.save();
}

数据源模板

# subgraph.yaml - 用于工厂模式
templates:
  - kind: ethereum
    name: Pool
    network: mainnet
    source:
      abi: Pool
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Pool
        - Swap
      abis:
        - name: Pool
          file: ./abis/Pool.json
      eventHandlers:
        - event: Swap(indexed address,uint256,uint256)
          handler: handleSwap
      file: ./src/pool.ts
// src/factory.ts
import { Pool as PoolTemplate } from "../generated/templates";

export function handlePoolCreated(event: PoolCreated): void {
  // 为新池创建数据源
  PoolTemplate.create(event.params.pool);

  // 创建Pool实体
  let pool = new Pool(event.params.pool.toHex());
  pool.save();
}

构建和部署

# 从模式生成代码
graph codegen

# 构建子图
graph build

# 认证
graph auth --studio <部署密钥>

# 部署到Subgraph Studio
graph deploy --studio my-subgraph

# 部署到托管服务(已弃用)
graph deploy --node https://api.thegraph.com/deploy/ \
  --ipfs https://api.thegraph.com/ipfs/ \
  username/my-subgraph

查询

# 示例查询
query RecentTransfers {
  transfers(first: 10, orderBy: timestamp, orderDirection: desc) {
    id
    from {
      id
      balance
    }
    to {
      id
      balance
    }
    value
    timestamp
  }
}

query TopHolders {
  accounts(first: 10, orderBy: balance, orderDirection: desc) {
    id
    balance
  }
}

query DailyVolumes {
  dailyVolumes(first: 30, orderBy: date, orderDirection: desc) {
    date
    volume
    txCount
  }
}

测试

// tests/token.test.ts
import { assert, test, clearStore } from "matchstick-as";
import { handleTransfer } from "../src/mapping";
import { createTransferEvent } from "./utils";

test("创建Transfer实体", () => {
  let event = createTransferEvent(
    "0x1234...",
    "0x5678...",
    "1000000000000000000"
  );

  handleTransfer(event);

  assert.entityCount("Transfer", 1);
  clearStore();
});

流程集成

流程 目的
subgraph-development.js 子图开发
blockchain-indexer-development.js 索引管道
dapp-frontend-development.js 数据集成

最佳实践

  1. 使用适当的startBlock避免重新索引
  2. 实现高效的实体更新
  3. 使用不可变实体处理重组
  4. 使用派生字段处理关系
  5. 使用Matchstick测试
  6. 监控索引性能

另请参阅

  • skills/wallet-integration/SKILL.md - dApp集成
  • agents/web3-frontend/AGENT.md - 前端专家
  • The Graph文档