子图定义了Graph将从以太坊中索引哪些数据以及如何存储。部署后,它将成为区块链数据全球图的一部分。

子图定义由几个文件组成:

  • subgraph.yaml:包含子图清单的YAML文件
  • schema.graphql:一个GraphQL模式,该模式定义为子图存储的数据以及如何通过GraphQL查询数据
  • AssemblyScript Mappings:从以太坊中的事件数据转换为架构中定义的实体的AssemblyScript代码(例如mapping.ts,本教程中的)

在详细了解清单文件的内容之前,需要安装Graph CLI ,这将需要构建和部署子图。

安装图形CLI

Graph CLI是用JavaScript编写的,您将需要安装 yarnnpm才能使用它;假定您具备yarn以下条件。详细的安装说明yarn 可以在 graph-cli仓库中找到

完成后yarn,通过运行以下命令安装Graph CLI

yarn global add @graphprotocol/graph-cli

创建一个子图项目

graph init命令可用于通过任何公共以太坊网络上的现有合同或示例子图来建立新的子图项目 。

如果您已经将智能合约部署到以太坊主网或其中一个测试网,则从该合约引导新的子图可能是入门的好方法。

以下命令创建一个子图,该子图索引现有合同的所有事件。它尝试从Etherscan获取合同ABI,然后回退至请求本地文件路径。如果缺少任何可选参数,它将带您进入交互式表格。

graph init \
--from-contract <CONTRACT_ADDRESS> \
[--network <ETHEREUM_NETWORK>] \
[--abi <FILE>] \
<GITHUB_USER>/<SUBGRAPH_NAME> [<DIRECTORY>]

<GITHUB_USER>是你的GitHub用户或组织名称,<SUBGRAPH_NAME>是名称为您子,并且<DIRECTORY>是所在的目录的名称(可选)graph init将把例如子清单。

<CONTRACT_ADDRESS>是您现有合同的地址。 <ETHEREUM_NETWORK>是合约所基于的以太坊网络的名称。<FILE>是合同ABI文件的本地路径。这两个--network--abi是可选的。

托管服务上支持的网络是:

  • mainnet
  • kovan
  • rinkeby
  • ropsten
  • goerli
  • poa-core
  • xdai
  • poa-sokol

第二种模式graph init支持从示例子图创建一个新项目 。下面的命令执行此操作:

graph init --from-example <GITHUB_USER>/<SUBGRAPH_NAME> [<DIRECTORY>]

该示例子图基于Dani GrantGravity合同 ,该合同管理用户头像,并在创建或更新头像时发出或事件。子图通过将实体写入图节点存储并确保根据事件更新这些实体来处理这些事件。以下各节将介绍构成此示例的子图清单的文件。NewGravatarUpdateGravatarGravatar

子图清单

子图清单subgraph.yaml定义了子图索引的智能合约,这些合约中要注意的事件以及如何将事件数据映射到Graph Node存储并允许查询的实体。子图清单的完整规范可以在这里找到 。

对于示例子图,subgraph.yaml为:

specVersion: 0.0.1
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/example-subgraph
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
abi: Gravity
startBlock: 6175244
mapping:
kind: ethereum/events
apiVersion: 0.0.1
language: wasm/assemblyscript
entities:
- Gravatar
abis:
- name: Gravity
file: ./abis/Gravity.json
eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- function: handleBlock
- function: handleBlockWithCall
filter:
kind: call
file: ./src/mapping.ts

为清单更新的重要条目是:

  • description:子图是什么的可读描述。当子图部署到Hosted Service时,图资源管理器将显示此描述。
  • repository:可以在其中找到子图清单的存储库的URL。图形资源管理器也会显示此内容。
  • dataSources.sourceaddress子图来源的智能合约的以及abi要使用的智能合约的。该address是可选的; 省略它允许索引所有合同的匹配事件。
  • dataSources.source.startBlock:数据源从其开始索引的块的可选编号。在大多数情况下,我们建议使用创建合同的区块。
  • dataSources.mapping.entities:数据源写入存储的实体。schema.graphql文件中定义了每个实体的架构。
  • dataSources.mapping.abis:一个或多个命名ABI文件,用于源合同以及您在映射中与之交互的任何其他智能合同。
  • dataSources.mapping.eventHandlers:列出此子图所响应的智能合约事件以及映射./src/mapping.ts中的处理程序(在示例中),这些处理程序将这些事件转换为商店中的实体。
  • dataSources.mapping.callHandlers:列出该子图对智能合约功能的反应以及映射中的处理程序,这些处理程序将输入和输出转换为功能调用,并将其转换为商店中的实体。
  • dataSources.mapping.blockHandlers:列出该子图对块的反应,并在将块附加到链中时运行映射中的处理程序。没有过滤器,块处理程序将在每个块上运行。可以提供以下几种可选过滤器:call。甲call如果块包含至少一个呼叫到数据源合同滤波器将运行该处理程序。

单个子图可以索引来自多个智能合约的数据。为需要从中将数据索引到dataSources数组的每个合同添加一个条目 。

使用以下过程对块内数据源的触发器进行排序:

  1. 事件和调用触发器首先按该块内的事务索引排序。
  2. 使用约定对同一事务中的事件和调用触发器进行排序:事件触发器先调用,然后调用触发器,每种类型都遵循清单中定义它们的顺序。
  3. 块触发器在事件和调用触发器之后按清单中定义的顺序运行。

这些订购规则可能会更改。

获取ABI

ABI文件必须与您的合同匹配。有几种获取ABI文件的方法:

  • 如果您正在构建自己的项目,则可能可以访问最新的ABI。
  • 如果要为公共项目构建子图,则可以将该项目下载到计算机上并通过使用truffle compile 或进行solc编译来获取ABI 。
  • 您还可以在Etherscan上找到ABI ,但这并不总是可靠的,因为在那里上传的ABI可能已过时。确保您具有正确的ABI,否则运行子图将失败。

GraphQL模式

子图的模式在文件中schema.graphql。GraphQL模式是使用GraphQL接口定义语言定义的。如果您从未编写过GraphQL模式,建议您在GraphQL类型系统上签出该 入门手册。GraphQL API部分中提供了GraphQL模式的参考文档 。

定义实体

在定义实体之前,重要的是要退后一步,考虑一下数据的结构和链接方式。将针对子图模式中定义的数据模型以及由子图索引的实体进行所有查询。因此,最好以符合dApp需求的方式定义子图模式。将实体想象为“包含数据的对象”,而不是事件或功能,可能会很有用。

使用Graph,您只需在中定义实体类型schema.graphql,并且Graph Node将生成顶级字段,用于查询该实体类型的单个实例和集合。每种应为实体​​的类型都必须使用@entity指令进行注释。

好的例子

Gravatar下面的实体围绕Gravatar对象构建,是如何定义实体的一个很好的例子。

type Gravatar @entity {
id: ID!
owner: Bytes
displayName: String
imageUrl: String
accepted: Boolean
}

不好的例子

下面的示例GravatarAcceptedGravatarDeclined实体基于事件。不建议将事件或函数调用映射到实体1:1。

type GravatarAccepted @entity {
id: ID!
owner: Bytes
displayName: String
imageUrl: String
}
type GravatarDeclined @entity {
id: ID!
owner: Bytes
displayName: String
imageUrl: String
}

可选和必填字段

实体字段可以定义为必填字段或可选字段。必填字段由!架构中的表示。如果未在映射中设置必填字段,则查询该字段时将收到此错误:

Null value resolved for non-null field 'name'

每个实体必须具有一个(字符串)id类型的字段ID!。该id字段用作主键,并且在相同类型的所有实体之间必须唯一。

内置标量类型

我们的GraphQL API支持以下标量:

类型描述Bytes字节数组,以十六进制字符串表示。常用于以太坊哈希和地址。ID储存为stringStringstring值的标量。不支持空字符,它们会被自动删除。Booleanboolean值的标量。IntGraphQL规范定义Int为具有32个字节的大小。BigInt大整数。用于复仇的uint32int64uint64,...,uint256的类型。注意:下面的一切uint32,例如int32uint24或者int8表示为i32BigDecimalBigDecimal高精度小数表示为符号和指数。指数范围是−6143至+6144。舍入到34位有效数字。

您还可以在架构中创建枚举。枚举具有以下语法:

enum TokenStatus {
OriginalOwner
SecondOwner
ThirdOwner
}

在架构中定义枚举后,就可以使用枚举值的字符串表示形式在实体上设置枚举字段。例如,您可以设置tokenStatusSecondOwner通过先定义你的实体,并随后与设置现场entity.tokenStatus = "SecondOwner。下面的示例Token通过枚举字段演示实体的外观:

type Token @entity {
id: ID!
name: String!
symbol: String!
decimals: Int!
tokenStatus: TokenStatus!
}

可以在GraphQL文档中找到有关编写枚举的更多详细信息

实体关系

实体可能与架构中的一个或多个其他实体有关系。您可以在查询中遍历这些关系。图中的关系是单向的。通过在关系的“两端”定义单向关系,可以模拟双向关系。

关系与其他字段一样,在实体上定义,所指定的类型是另一个实体的类型。

定义一个Transaction与实体类型具有可选的一对一关系的TransactionReceipt实体类型:

type Transaction @entity {
id: ID!
transactionReceipt: TransactionReceipt
}
type TransactionReceipt @entity {
id: ID!
transaction: Transaction
}

定义一个TokenBalance与实体类型具有必要的一对多关系的Token实体类型:

type Token @entity {
id: ID!
}
type TokenBalance @entity {
id: ID!
amount: Int!
token: Token!
}

反向查询

可以通过@derivedFrom 字段在实体上定义反向查找。这会在实体上创建一个虚拟字段,但可以通过映射API手动设置该字段,但无法查询。相反,它是从另一个实体上定义的关系派生的。对于这样的关系,存储关系的两边几乎没有意义,并且仅存储一侧而派生另一侧时,索引和查询性能会更好。

对于一对多关系,该关系应始终存储在“一个”侧,并且应始终派生“许多”侧。以这种方式存储关系,而不是在“许多”侧存储实体数组,将为索引和查询子图带来显着更好的性能。通常,应尽可能避免存储实体数组。

我们可以通过导出tokenBalances字段来使令牌的余额可从令牌访问:

type Token @entity {
id: ID!
tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")
}
type TokenBalance @entity {
id: ID!
amount: Int!
token: Token!
}

对于多对多关系,例如每个用户可能属于任意数量的组织的用户,建立关系模型的最直接但通常不是最有效的方法是在涉及的两个实体中的每一个中作为一个数组。如果关系是对称的,则仅需要存储关系的一侧,而可以导出另一侧。

定义从User实体类型到Organization实体类型的反向查找。在下面的示例中,这是通过membersOrganization实体内部查找属性来实现的。在查询中,将通过查找包含用户ID的所有实体来解析organizations字段on 。UserOrganization

type Organization @entity {
id: ID!
name: String!
members: [User!]!
}
type User @entity {
id: ID!
name: String!
organizations: [Organization!]! @derivedFrom(field: "members")
}

存储这种关系的一种更有效的方法是通过映射表,该映射表为每个User/Organization对具有一个条目,并具有如下模式

type Organization @entity {
id: ID!
name: String!
members: [UserOrganization]! @derivedFrom(field: "user")
}
type User @entity {
id: ID!
name: String!
organizations: [UserOrganization!] @derivedFrom(field: "organization")
}
type UserOrganization @entity {
id: ID! # Set to `${user.id}-${organization.id}`
user: User!
organization: Organization!
}

这种方法要求查询下降到另一个级别以检索例如用户的组织:

query usersWithOrganizations {
users {
organizations { # this is a UserOrganization entity
organization {
name
}
}
}
}

这种存储多对多关系的更加精细的方法将导致为子图存储的数据更少,因此,为子图存储的数据通常会大大加快索引和查询的速度。

向架构添加注释

根据GraphQL规范,可以使用双引号在架构实体属性上方添加注释""。在下面的示例中对此进行了说明:

type MyFirstEntity @entity {
"unique identifier and primary key of the entity"
id: ID!
address: Bytes!
}

定义全文搜索字段

全文搜索查询根据文本搜索输入对实体进行过滤和排名。全文查询能够通过在与索引文本数据进行比较之前将查询文本输入处理为词干,来返回相似单词的匹配项。

全文查询定义包括查询名称,用于处理文本字段的语言词典,用于对结果进行排序的排名算法以及搜索中包含的字段。每个全文查询可以跨越多个字段,但是所有包含的字段必须来自单个实体类型。

要添加全文查询,请_Schema_在GraphQL模式中包括带有全文指令的类型。

type _Schema_
@fulltext(
name: "bandSearch",
language: en
algorithm: rank,
include: [
{
entity: "Band",
fields: [
{ name: "name" },
{ name: "description" },
{ name: "bio" },
]
}
]
)
type Band @entity {
id: ID!
name: String!
description: String!
bio: String
wallet: Address
labels: [Label!]!
discography: [Album!]!
members: [Musician!]!
}

这个例子bandSearch字段可以在查询过滤器中使用Band基于文本的文档实体namedescriptionbio领域。跳转至GraphQL API-查询全文搜索API的说明以及更多示例用法。

query {
bandSearch(text: "breaks & electro & detroit") {
id
name
description
wallet
}
}

支持的语言

选择不同的语言将对全文搜索API产生确定的(尽管有时是微妙的)影响。全文查询字段覆盖的字段是在所选语言的上下文中检查的,因此分析和搜索查询生成的词素会因语言而异。例如:当使用支持的土耳其语词典时,“ token”的词根为“ toke”,而英语词典当然会将其作为“ token”的词根。

支持的语言词典:

码字典简单一般DA丹麦文nl荷兰语恩英语科幻芬兰fr法文德德语hu匈牙利它义大利文没有挪威pt葡萄牙语RO罗马尼亚语RU俄语es西班牙文sv瑞典tr土耳其

排名算法

支持的排序结果算法:

算法描述秩使用全文查询的匹配质量(0–1)对结果进行排序。近距离排名类似于rank但也包括比赛的接近性。

编写映射

映射将映射所获取的以太坊数据转换为架构中定义的实体。映射以TypeScript的子集( 称为 AssemblyScript) 编写,可以将其编译为WASM(WebAssembly)。AssemblyScript比普通的TypeScript严格,但提供了熟悉的语法。

对于在定义的每个事件处理程序subgraph.yamlmapping.eventHandlers,创建具有相同名称的导出函数。每个处理程序必须接受一个调用的参数event,该参数的类型与要处理的事件的名称相对应。

在示例子图中,src/mapping.ts包含NewGravatarUpdatedGravatar事件的处理程序 :

import { NewGravatar, UpdatedGravatar } from '../generated/Gravity/Gravity'
import { Gravatar } from '../generated/schema'
export function handleNewGravatar(event: NewGravatar): void {
let gravatar = new Gravatar(event.params.id.toHex())
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatar): void {
let id = event.params.id.toHex()
let gravatar = Gravatar.load(id)
if (gravatar == null) {
gravatar = new Gravatar(id)
}
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}

第一个处理程序接受一个NewGravatar事件,并使用创建一个新Gravatar 实体new Gravatar(event.params.id.toHex()),使用相应的事件参数填充实体字段。该实体实例由变量表示gravatarid值为event.params.id.toHex()

第二个处理程序尝试Gravatar从“图形节点”存储中加载现有文件。如果尚不存在,则按需创建。然后,在使用将该实体保存回商店之前,对该实体进行更新以匹配新的事件参数gravatar.save()

创建新实体的推荐ID

实体必须具有id在相同类型的所有实体中唯一的。id创建实体时设置实体的值。以下是id创建新实体时要考虑的一些建议值。注意:的值id必须是string

  • event.params.id.toHex()
  • event.transaction.from.toHex()
  • event.transaction.hash.toHex() + "-" + event.logIndex.toString()

我们提供了 Graph Typescript库,其中包含用于与Graph Node存储进行交互的实用程序,以及用于处理智能合约数据和实体的便利工具。您可以通过导入来@graphprotocol/graph-ts在映射中使用此库mapping.ts

代码生成

为了使工作中的智能合约,事件和实体变得容易且类型安全,Graph CLI可以从子图的GraphQL模式和数据源中包含的合约ABI生成AssemblyScript类型。

这是用

graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]

但是在大多数情况下,子图已经通过package.json进行了预配置,以使您可以简单地运行以下其中一项来实现相同的目的:

# Yarn
yarn codegen
# NPM
npm run codegen

这将为中提到的ABI文件中的每个智能合约生成一个AssemblyScript类subgraph.yaml,允许您将这些合约绑定到映射中的特定地址,并针对正在处理的块调用只读合约方法。它还将为每个合同事件生成一个类,以提供对事件参数以及事件源自的冻结和交易的轻松访问。所有这些类型都写入<OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.ts。在示例子图中,这将是generated/Gravity/Gravity.ts,允许映射使用

import {
// The contract class:
Gravity,
// The events classes:
NewGravatar,
UpdatedGravatar,
} from '../generated/Gravity/Gravity'

除此之外,还会为子图的GraphQL模式中的每种实体类型生成一个类。这些类提供了类型安全的实体加载,对实体字段的读写访问以及save()用于写入要存储的实体的方法。所有实体类都写入<OUTPUT_DIR>/schema.ts,从而允许映射将其导入

import { Gravatar } from '../generated/schema'

注意:每次更改GraphQL模式或清单中包含的ABI后,必须再次执行代码生成。在构建或部署子图之前,还必须至少执行一次。

代码生成不会检查中的映射代码src/mapping.ts。如果要在尝试将子图部署到Graph Explorer之前进行检查,则可以运行yarn build并修复TypeScript编译器可能发现的所有语法错误。

如果您的合同的只读方法可以还原,则应通过调用以开头的已生成合同方法来处理该问题try_。例如,重力合同公开了该gravatarToOwner方法。此代码将能够处理该方法中的还原:

let gravity = Gravity.bind(event.address)
let callResult = gravity.try_gravatarToOwner(gravatar)
if (callResult.reverted) {
log.info("getGravatar reverted", [])
} else {
let owner = callResult.value
}

请注意,连接到Geth或Infura客户端的Graph节点可能无法检测到所有还原,如果您依靠它,我们建议使用连接到Parity客户端的Graph节点。

在下一节中,我们将描述如何使用Graph Explorer部署子图。

数据源模板

以太坊智能合约的一种常见模式是使用注册表或工厂合约,其中一个合约创建,管理或引用任意数量的其他合约,每个合约都有各自的状态和事件。这些分包合同的地址可能事先知道,也可能不知道,随着时间的推移,可能会创建和/或添加许多这些合同。因此,在这种情况下,不可能定义单个数据源或固定数量的数据源,而需要一种更动态的方法:数据源模板

主合同的数据源

首先,为主合同定义常规数据源。下面的代码片段显示了Uniswap 交换工厂合同的简化示例数据源。注意NewExchange(address,address)事件处理程序。当工厂合同在链上创建新的交换合同时,将发出此消息。

dataSources:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: Factory
mapping:
kind: ethereum/events
apiVersion: 0.0.2
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- Directory
abis:
- name: Factory
file: ./abis/factory.json
eventHandlers:
- event: NewExchange(address,address)
handler: handleNewExchange

动态创建合同的数据源模板

然后,您将数据源模板添加到清单。除了缺少之下的预定义合同地址外,这些数据与常规数据源相同 source。通常,您将为父合同管理或引用的每种子合同类型定义一个模板。

dataSources:
- kind: ethereum/contract
name: Factory
# ... other source fields for the main contract ...
templates:
- name: Exchange
kind: ethereum/contract
network: mainnet
source:
abi: Exchange
mapping:
kind: ethereum/events
apiVersion: 0.0.1
language: wasm/assemblyscript
file: ./src/mappings/exchange.ts
entities:
- Exchange
abis:
- name: Exchange
file: ./abis/exchange.json
eventHandlers:
- event: TokenPurchase(address,uint256,uint256)
handler: handleTokenPurchase
- event: EthPurchase(address,uint256,uint256)
handler: handleEthPurchase
- event: AddLiquidity(address,uint256,uint256)
handler: handleAddLiquidity
- event: RemoveLiquidity(address,uint256,uint256)
handler: handleRemoveLiquidity

实例化数据源模板

在最后一步中,您将更新主合同映射以从其中一个模板创建动态数据源实例。在此示例中,您将更改主合同映射以导入Exchange模板并Exchange.create(address)在其上调用方法以开始为新的交换合同建立索引。

import { Exchange } from '../generated/templates'export function handleNewExchange(event: NewExchange): void {
// Start indexing the exchange; `event.params.exchange` is the
// address of the new exchange contract
Exchange.create(event.params.exchange)
}

注意:一个新的数据源将只处理在其中创建它的块和所有后续块的调用和事件,而不会处理历史数据,即先前块中包含的数据。

如果先前的块包含与新数据源相关的数据,则最好通过读取合同的当前状态并在创建新数据源时创建代表该状态的实体来为该数据建立索引。

数据源上下文

数据源上下文允许在实例化模板时传递额外的配置。在我们的示例中,假设交易所与NewExchange事件中包含的特定交易对相关联。该信息可以传递到实例化的数据源中,如下所示:

import { Exchange } from '../generated/templates'export function handleNewExchange(event: NewExchange): void {
let context = new DataSourceContext()
context.setString("tradingPair", event.params.tradingPair)
Exchange.createWithContext(event.params.exchange, context)
}

Exchange模板的映射内,可以访问上下文:

import { dataSource } from "@graphprotocol/graph-ts"let context = dataSource.context()
let tradingPair = context.getString("tradingPair")

有getter和setter方法一样setString,并getString为所有的值类型。

起始块

startBlock是一个可选的设置,允许你定义从哪个块链中的数据源将开始索引。设置起始块可以使数据源跳过可能不相关的数百万个块。通常,子图开发人员将设置startBlock为在其中创建数据源智能合约的块。

dataSources:
- kind: ethereum/contract
name: ExampleSource
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: ExampleContract
startBlock: 6627917
mapping:
kind: ethereum/events
apiVersion: 0.0.2
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- User
abis:
- name: ExampleContract
file: ./abis/ExampleContract.json
eventHandlers:
- event: NewEvent(address,address)
handler: handleNewEvent

注意:可以在Etherscan上快速查找合同创建块:1.通过在搜索栏中输入其地址来搜索合同。2.单击该Contract Creator部分中的创建事务哈希。3.加载交易明细页面,您将在其中找到该合同的起始块。

呼叫处理程序

事件为收集合同状态的相关更改提供了一种有效的方法,但许多合同都避免生成日志以优化天然气成本。在这些情况下,子图可以预订对数据源合同的调用。这是通过定义引用函数签名的调用处理程序以及将处理该函数调用的映射处理程序来实现的。为了处理这些调用,映射处理程序将接收一个ethereum.Callas作为参数,并带有该调用的类型输入和输出。在事务的呼叫链中任何深度进行的呼叫都将触发映射,从而允许捕获通过代理合同进行的数据源合同的活动。

调用处理程序将仅在以下两种情况之一中触发:指定的功能由合同本身以外的帐户调用,或者在Solidity中被标记为外部功能并在同一合同中作为另一个功能的一部分被调用时。

注意: Rinkeby和Ganache不支持呼叫处理程序。呼叫处理程序取决于奇偶校验跟踪API,Rinkeby和Ganache均不支持此功能(Rinkeby仅适用于Geth)。

定义呼叫处理程序

要在清单中定义呼叫处理程序,只需callHandlers在要订阅的数据源下添加一个数组。

dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.2
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar

function是标准化函数签名通过过滤器的调用。该 handler属性是您在映射中要在数据源协定中调用目标函数时要执行的函数的名称。

映射功能

每个调用处理程序都采用一个参数,该参数的类型与被调用函数的名称相对应。在上面的示例子图中,映射包含用于何时createGravatar调用函数的处理程序,并接收 CreateGravatarCall参数作为参数:

import { CreateGravatarCall } from '../generated/Gravity/Gravity'
import { Transaction } from '../generated/schema'
export function handleCreateGravatar(call: CreateGravatarCall): void {
let id = call.transaction.hash.toHex()
let transaction = new Transaction(id)
transaction.displayName = call.inputs._displayName
transaction.imageUrl = call.inputs._imageUrl
transaction.save()
}

handleCreateGravatar函数采用new CreateGravatarCall,它是的的子类ethereum.Call,由所提供@graphprotocol/graph-ts,其中包括调用的类型化输入和输出。在CreateGravatarCall当您运行类型为您生成graph codegen

块处理程序

除了订阅合同事件或函数调用外,子图可能还需要在将新块附加到链时更新其数据。为此,子图可以在每个块之后或与预定义过滤器匹配的块之后运行函数。

支持的过滤器

filter:
kind: call

对于每个包含对定义了该处理程序的合约(数据源)的调用的块,都会对其定义的处理程序调用一次。

块处理程序缺少过滤器将确保该处理程序被每个块调用。对于每种过滤器类型,一个数据源只能包含一个块处理程序。

dataSources:
- kind: ethereum/contract
name: Gravity
network: dev
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.2
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithCallToContract
filter:
kind: call

映射功能

映射函数将收到一个ethereum.Block作为其唯一参数。类似于事件的映射功能,此功能可以访问商店中的现有子图实体,调用智能合约并创建或更新实体。

import { ethereum } from '@graphprotocol/graph-ts'export function handleBlock(block: ethereum.Block): void {
let id = block.hash.toHex()
let entity = new Block(id)
entity.save()
}

匿名事件

如果您需要在Solidity中处理匿名事件,可以通过提供事件的主题0来实现,如示例中所示:

eventHandlers:
- event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)
topic0: "0xbaa8529c00000000000000000000000000000000000000000000000000000000"
handler: handleGive

仅当签名和主题0都匹配时才触发事件。默认情况下,topic0等于事件签名的哈希值。

IPFS固定

结合使用IPFS和以太坊的一个常见用例是将数据存储在IPFS上,这对于链上的维护来说太昂贵了,并在以太坊合约中引用IPFS哈希。

给定这样的IPFS散列,子图可以使用ipfs.cat和从IPFS读取相应的文件ipfs.map。但是,为了可靠地执行此操作,需要将这些文件固定在索引子图的图节点所连接的IPFS节点上。对于托管服务,这是 https://api.thegraph.com/ipfs/

为了使这个简单的子开发商,图表团队从一个IPFS节点到另一个节点,称为传送文件的写了一个工具 IPF问题同步