名称: 子图索引 描述: 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 |
数据集成 |
最佳实践
- 使用适当的startBlock避免重新索引
- 实现高效的实体更新
- 使用不可变实体处理重组
- 使用派生字段处理关系
- 使用Matchstick测试
- 监控索引性能
另请参阅
skills/wallet-integration/SKILL.md- dApp集成agents/web3-frontend/AGENT.md- 前端专家- The Graph文档