TheGraph 二 :subgraph 四大关键定义数据
我们以 Uniswap V2 的 subgraph 为例,来详细解释 subgraph 的四个关键定义数据。
一、SubgraphManifest
Uniswap V2 的 subgraph.yaml
文件就是其 SubgraphManifest
,定义了这个 subgraph 的基本信息。
specVersion: 0.0.2
description: Uniswap is a decentralized protocol for automated token exchange on Ethereum.
repository: https://github.com/Uniswap/uniswap-v2-subgraph
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
abi: Factory
startBlock: 10000835
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
entities:
- Pair
- Token
abis:
- name: Factory
file: ./abis/factory.json
- name: Pair
file: ./abis/pair.json
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: PairCreated(indexed address,indexed address,address,uint256)
handler: handlePairCreated
file: ./src/mappings/factory.ts
- kind: ethereum/contract
name: Pair
network: mainnet
source:
abi: Pair
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
entities:
- Pair
- Token
abis:
- name: Pair
file: ./abis/pair.json
- name: Factory
file: ./abis/factory.json
eventHandlers:
- event: Mint(indexed address,uint256,uint256)
handler: handleMint
- event: Burn(indexed address,uint256,uint256,indexed address)
handler: handleBurn
- event: Swap(indexed address,uint256,uint256,uint256,uint256,indexed address)
handler: handleSwap
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
- event: Sync(uint112,uint112)
handler: handleSync
file: ./src/mappings/core.ts
这个 manifest 定义了两个数据源:Factory
和 Pair
,分别对应 Uniswap V2 的核心合约。每个数据源都指定了网络、合约地址、ABI、起始区块等信息,以及相应的 mapping 函数。
二、DataSource
以 Factory
数据源为例:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
abi: Factory
startBlock: 10000835
这里定义了如何从以太坊主网上的 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
地址获取 Factory
合约的数据,使用 Factory
的 ABI 解析数据,从区块 10000835 开始同步。
三、Mapping 函数
继续看 Factory
的 mapping 定义:
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
entities:
- Pair
- Token
abis:
- name: Factory
file: ./abis/factory.json
- name: Pair
file: ./abis/pair.json
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: PairCreated(indexed address,indexed address,address,uint256)
handler: handlePairCreated
file: ./src/mappings/factory.ts
这里定义了 Factory
数据源的 mapping 函数。它监听 PairCreated
事件,当事件发生时,调用 handlePairCreated
函数处理事件数据。
handlePairCreated
函数在 ./src/mappings/factory.ts
文件中定义:
export function handlePairCreated(event: PairCreated): void {
// load factory
let factory = Factory.load(FACTORY_ADDRESS)
if (factory === null) {
factory = new Factory(FACTORY_ADDRESS)
factory.pairCount = 0
factory.totalVolumeETH = ZERO_BD
factory.totalLiquidityETH = ZERO_BD
factory.totalVolumeUSD = ZERO_BD
factory.untrackedVolumeUSD = ZERO_BD
factory.totalLiquidityUSD = ZERO_BD
factory.txCount = ZERO_BI
factory.mostLiquidTokens = []
}
factory.pairCount = factory.pairCount + 1
factory.save()
// create the tokens
let token0 = Token.load(event.params.token0.toHexString())
let token1 = Token.load(event.params.token1.toHexString())
// fetch info if null
if (token0 === null) {
token0 = new Token(event.params.token0.toHexString())
token0.symbol = fetchTokenSymbol(event.params.token0)
token0.name = fetchTokenName(event.params.token0)
token0.totalSupply = fetchTokenTotalSupply(event.params.token0)
let decimals = fetchTokenDecimals(event.params.token0)
// bail if we couldn't figure out the decimals
if (decimals === null) {
log.debug('mybug the decimal on token 0 was null', [])
return
}
token0.decimals = decimals
token0.derivedETH = ZERO_BD
token0.tradeVolume = ZERO_BD
token0.tradeVolumeUSD = ZERO_BD
token0.untrackedVolumeUSD = ZERO_BD
token0.totalLiquidity = ZERO_BD
// token0.allPairs = []
token0.txCount = ZERO_BI
}
// fetch info if null
if (token1 === null) {
token1 = new Token(event.params.token1.toHexString())
token1.symbol = fetchTokenSymbol(event.params.token1)
token1.name = fetchTokenName(event.params.token1)
token1.totalSupply = fetchTokenTotalSupply(event.params.token1)
let decimals = fetchTokenDecimals(event.params.token1)
// bail if we couldn't figure out the decimals
if (decimals === null) {
return
}
token1.decimals = decimals
token1.derivedETH = ZERO_BD
token1.tradeVolume = ZERO_BD
token1.tradeVolumeUSD = ZERO_BD
token1.untrackedVolumeUSD = ZERO_BD
token1.totalLiquidity = ZERO_BD
// token1.allPairs = []
token1.txCount = ZERO_BI
}
let pair = new Pair(event.params.pair.toHexString()) as Pair
pair.token0 = token0.id
pair.token1 = token1.id
pair.liquidityProviderCount = ZERO_BI
pair.createdAtTimestamp = event.block.timestamp
pair.createdAtBlockNumber = event.block.number
pair.txCount = ZERO_BI
pair.reserve0 = ZERO_BD
pair.reserve1 = ZERO_BD
pair.trackedReserveETH = ZERO_BD
pair.reserveETH = ZERO_BD
pair.reserveUSD = ZERO_BD
pair.totalSupply = ZERO_BD
pair.volumeToken0 = ZERO_BD
pair.volumeToken1 = ZERO_BD
pair.volumeUSD = ZERO_BD
pair.untrackedVolumeUSD = ZERO_BD
pair.token0Price = ZERO_BD
pair.token1Price = ZERO_BD
// create the tracked contract based on the template
PairTemplate.create(event.params.pair)
// save updated values
token0.save()
token1.save()
pair.save()
factory.save()
}
这个函数处理 PairCreated
事件,创建了 Factory
、Token
和 Pair
实体,并设置它们的初始值。这些实体对应了 schema 中定义的数据类型。
四、Schema
Uniswap V2 的 schema 定义在 schema.graphql
文件中:
type Factory @entity {
id: ID!
pairCount: Int!
totalVolumeUSD: BigDecimal!
totalVolumeETH: BigDecimal!
untrackedVolumeUSD: BigDecimal!
totalLiquidityUSD: BigDecimal!
totalLiquidityETH: BigDecimal!
txCount: BigInt!
mostLiquidTokens: [TokenDayData!]!
}
type Token @entity {
id: ID!
symbol: String!
name: String!
decimals: BigInt!
totalSupply: BigInt!
tradeVolume: BigDecimal!
tradeVolumeUSD: BigDecimal!
untrackedVolumeUSD: BigDecimal!
txCount: BigInt!
totalLiquidity: BigDecimal!
derivedETH: BigDecimal!
mostLiquidPairs: [PairDayData!]!
}
type Pair @entity {
id: ID!
token0: Token!
token1: Token!
reserve0: BigDecimal!
reserve1: BigDecimal!
totalSupply: BigDecimal!
reserveETH: BigDecimal!
reserveUSD: BigDecimal!
trackedReserveETH: BigDecimal!
token0Price: BigDecimal!
token1Price: BigDecimal!
volumeToken0: BigDecimal!
volumeToken1: BigDecimal!
volumeUSD: BigDecimal!
untrackedVolumeUSD: BigDecimal!
txCount: BigInt!
createdAtTimestamp: BigInt!
createdAtBlockNumber: BigInt!
liquidityProviderCount: BigInt!
}
# ...
这里定义了 Factory
、Token
和 Pair
三个主要的实体类型,以及它们的字段。这些定义决定了 subgraph 的数据模型,也就是 mapping 函数的输出结构,以及 GraphQL API 的查询接口。
综上所述,通过 SubgraphManifest
定义数据源和 mapping 函数,通过 mapping 函数将区块链数据转换为 schema 定义的数据模型,最终生成符合 schema 定义的 GraphQL API,这就是一个完整的 subgraph 开发流程。
在实际开发中,我们主要需要关注 schema 的设计和 mapping 函数的实现。schema 决定了数据模型和 API 接口,mapping 函数决定了数据的转换逻辑。一个好的 subgraph 设计,应该能够抽象出清晰、合理的数据模型,并通过 mapping 函数高效、准确地完成数据转换。