使用The Graph官方的Subgraph Studio在线平台来监控合约事件
The Graph 是一个去中心化的区块链数据索引协议,让你可以像查询数据库一样,用 GraphQL 查询区块链数据。
区块链上的数据虽然公开,但查询效率低、结构复杂,想查某个合约的交互历史,要从成千上万个区块中找,太麻烦了。 而The Graph的作用就是提前监听区块链事件,把你关心的数据抓出来、处理好、存到数据库,然后开放查询接口。
大概流程:
便于理解:
Subgraph Studio 是 The Graph 官方提供的图形化工具,用来创建、测试、部署和管理 Subgraph 的 Web 平台。
官方教程:https://thegraph.com/docs/zh/subgraphs/quick-start/
我们在此平台上来监听Counter合约的事件,监听事件包括:SetNumber和Increment,来监听number的变化;
准备条件
在本地电脑上安装graph客户端
# npm安装graph客户端
$ npm install -g @graphprotocol/graph-cli@latest
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 422 packages in 22s
86 packages are looking for funding
run `npm fund` for details
# 查看客户端版本
$ graph --version
@graphprotocol/graph-cli/0.97.0 win32-x64 node-v23.11.0
根据执行输入参数
$ graph init counter
√ Network · Ethereum Sepolia Testnet · sepolia · https://sepolia.etherscan.io # 完整输入Ethereum Sepolia Testnet,选择sepolia网络
√ Source · Smart Contract · ethereum # 回车
√ Subgraph slug · counter # 回车
√ Directory to create the subgraph in · counter # 回车
√ Contract address · 0xB78473a261797b9Fa4a18adff4Ad89B2b94B8856 # 输入验证通过的合约地址
√ Fetching ABI from Sourcify API...
√ Fetching ABI from Contract API...
√ Fetching start block from Contract API...
√ Fetching contract name from Contract API...
√ Start block · 8310332 # 回车,确认部署合约的区块号
? Contract name » counter # 回车
√ Contract name · counter
√ Index contract events as entities (Y/n) · true # 回车
Generate subgraph
Write subgraph to directory
√ Create subgraph scaffold
√ Initialize networks config
√ Initialize subgraph repository
√ Install dependencies with npm install
√ Generate ABI and schema types with npm run codegen
√ Add another contract? (y/N) · false # 回车
Subgraph counter created in counter
Next steps:
1. Run `graph auth` to authenticate with your deploy key.
2. Type `cd counter` to enter the subgraph.
3. Run `npm run deploy` to deploy the subgraph.
Make sure to visit the documentation on https://thegraph.com/docs/ for further information.
授权码在web端中查找 deploy key
$ graph auth 635702bb8e772ac010c612105bd4b2f4
Deploy key set for https://api.studio.thegraph.com/deploy/
三个核心文件:
# 进入初始化项目目录
$ cd counter/
# 生成和编译
$ graph codegen && graph build
Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
√ Apply migrations
√ Load subgraph from subgraph.yaml # 数据来源和处理规则
Load contract ABI from abis\counter.json
√ Load contract ABIs
Generate types for contract ABI: counter (abis\counter.json)
Write types to generated\counter\counter.ts
√ Generate types for contract ABIs
√ Generate types for data source templates
√ Load data source template ABIs
√ Generate types for data source template ABIs
√ Load GraphQL schema from schema.graphql # 解析数据的实体
Write types to generated\schema.ts
√ Generate types for GraphQL schema
Types generated successfully # 生成成功
Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
√ Apply migrations
√ Load subgraph from subgraph.yaml
Compile data source: counter => build\counter\counter.wasm
√ Compile subgraph
Copy schema file build\schema.graphql
Write subgraph file build\counter\counter.json
Write subgraph manifest build\subgraph.yaml
√ Write compiled subgraph to build\
Build completed: build\subgraph.yaml # 编译完成
生成的schema.graphql文件内容
定义解析数据的结构,即作为存储到PostgreSQL的实体结构,也是前端查询接口的传参;
type Increment @entity(immutable: true) { # @entity 表示这是需要被GraphNode存储到PostgreSQL的数据
id: Bytes! # 所有 Entity 都需要有一个 id 字段
sender: Bytes! # address
newValue: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type SetNumber @entity(immutable: true) {
id: Bytes!
sender: Bytes! # address
newNumber: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
定义数据来源和处理规则;告诉GraphNode监听哪些合约,从哪个区块开始,匹配哪些事件,调用哪个处理函数等;
specVersion: 1.3.0
indexerHints:
prune: auto
schema:
file: ./schema.graphql
dataSources: # 要监听的链上合约及其事件
- kind: ethereum
name: counter
network: sepolia
source:
address: "0xB78473a261797b9Fa4a18adff4Ad89B2b94B8856"
abi: counter # 对应事件的 ABI 文件,用于解析日志
startBlock: 8310332 # 指定从哪个区块开始监听(提升效率)
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities:
- Increment
- SetNumber
abis:
- name: counter
file: ./abis/counter.json
eventHandlers: # 指定事件和处理函数的映射关系
- event: Increment(indexed address,uint256)
handler: handleIncrement
- event: SetNumber(indexed address,uint256)
handler: handleSetNumber
file: ./src/counter.ts # 指定映射逻辑所在的 TypeScript 文件
该案例的mapping.ts 对应为 counter/src/counter.ts;
定义事件的处理逻辑,将解析数据转换为实体存入PostgreSQL;
import {
Increment as IncrementEvent,
SetNumber as SetNumberEvent
} from "../generated/counter/counter"
import { Increment, SetNumber } from "../generated/schema"
export function handleIncrement(event: IncrementEvent): void {
let entity = new Increment( // 监听到 IncrementEvent 后,创建一个entity实体。
event.transaction.hash.concatI32(event.logIndex.toI32())
)
entity.sender = event.params.sender // 利用事件参数填充字段
entity.newValue = event.params.newValue
entity.blockNumber = event.block.number
entity.blockTimestamp = event.block.timestamp
entity.transactionHash = event.transaction.hash
entity.save() // 然后 .save() 保存进数据库。
}
export function handleSetNumber(event: SetNumberEvent): void {
let entity = new SetNumber(
event.transaction.hash.concatI32(event.logIndex.toI32())
)
entity.sender = event.params.sender
entity.newNumber = event.params.newNumber
entity.blockNumber = event.block.number
entity.blockTimestamp = event.block.timestamp
entity.transactionHash = event.transaction.hash
entity.save()
}
将三个核心文件上传IPFS,并部署Graph node
$ graph deploy counter
# 正确结果
Which version label to use? (e.g. "v0.0.1") · v0.0.1 # 输入版本标签,用于标识本次部署的子图版本
Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
# 编译输出的关键文件(已准备部署)
Write subgraph file build\counter\counter.json # subgraph.yaml 编译后的 JSON 格式配置
Write subgraph manifest build\subgraph.yaml # 拷贝原始 manifest 文件用于 build 目录中的部署引用
√ Write compiled subgraph to build\ # 编译完成,所有核心文件写入 build 目录
# 将核心文件上传到 IPFS(去中心化内容地址存储)
Add file to IPFS build\schema.graphql # GraphQL 实体结构定义(决定可查询字段)
.. QmezFjzFxkKzgjYZzNgM67kEbwFWFqYSCjrGmXwLSsfas5# IPFS文件的唯一哈希值,保证唯一防篡改
Add file to IPFS build\counter\counter.json # 索引说明书(合约地址、处理事件、mapping 指向等)
.. QmUp4K2wbtfL6BS6RkcPmqMrEgzDJFbiG8spBtVFY7cPCX
Add file to IPFS build\counter\counter.wasm # 编译后的 mapping.ts 文件(事件处理逻辑)
.. QmU7rqqieWR3v8rcxwtEvDkWM12ymA4E5bK5eHtpf2zCUa
√ Upload subgraph to IPFS # 上传成功!
Build completed: QmQ3iiVTZByx28jUuN1nXPAHr2JcwxgjfAVjDa9gYvZhyc # 整个子图的 IPFS 根哈希(代表本次部署的唯一标识)
- Deploying to Graph node https://api.studio.thegraph.com/depl
\ Deploying to Graph node https://api.studio.thegraph.com/depl
| Deploying to Graph node https://api.studio.thegraph.com/depl
/ Deploying to Graph node https://api.studio.thegraph.com/depl
- Deploying to Graph node https://api.studio.thegraph.com/depl
\ Deploying to Graph node https://api.studio.thegraph.com/depl
| Deploying to Graph node https://api.studio.thegraph.com/depl
/ Deploying to Graph node https://api.studio.thegraph.com/depl
- Deploying to Graph node https://api.studio.thegraph.com/depl
\ Deploying to Graph node https://api.studio.thegraph.com/depl
| Deploying to Graph node https://api.studio.thegraph.com/depl
/ Deploying to Graph node https://api.studio.thegraph.com/depl
- Deploying to Graph node https://api.studio.thegraph.com/depl
\ Deploying to Graph node https://api.studio.thegraph.com/depl
| Deploying to Graph node https://api.studio.thegraph.com/depl
/ Deploying to Graph node https://api.studio.thegraph.com/depl
- Deploying to Graph node https://api.studio.thegraph.com/depl # 部署到graph node节点
Deployed to https://thegraph.com/studio/subgraph/counter # 部署到studio中
部署完成后,在回到web端就会发生变化
当我调用Counter合约的两个函数时,etherscan 和 subgraph studio上都能看到发生的事件;
合约事件:https://sepolia.etherscan.io/address/0xB78473a261797b9Fa4a18adff4Ad89B2b94B8856#events
Subgraph查询:https://thegraph.com/studio/subgraph/counter/playground
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!