AssemblyScript API(二)

存储 API

import { store } from '@graphprotocol/graph-ts'

存储 API 允许从 HyperGraph 节点存储中加载、保存和删除实体。

写入存储映射表的实体与子图的 schema.graphql 文件中定义的@entity 类型是一一对应的。为了方便使用这些实体,Graph CLI (HyperGraph 请使用 @hgdotnetwork/graph-cli )提供的graph codegen命令生成实体类,它们是内置Entity类型的子类,具有Schema 中字段的属性 getter和setter以及load(加载)和save(保存)方法来操作这些实体。

创建实体

以下是基于以太坊事件创建实体的常见模式。

// Import the Transfer event class generated from the ERC20 ABI
import { Transfer as TransferEvent } from '../generated/ERC20/ERC20'

// Import the Transfer entity type generated from the GraphQL schema
import { Transfer } from '../generated/schema'

// Transfer event handler
export function handleTransfer(event: TransferEvent): void {
  // Create a Transfer entity, using the hexadecimal string representation
  // of the transaction hash as the entity ID
  let id = event.transaction.hash.toHex()
  let transfer = new Transfer(id)

  // Set properties on the entity, using the event parameters
  transfer.from = event.params.from
  transfer.to = event.params.to
  transfer.amount = event.params.amount

  // Save the entity to the store
  transfer.save()
}

在处理区块链数据遇到 Transfer 事件时,它将使用生成的 Transfer 类型(实为 TransferEvent 的别名,以避免与实体类型的命名冲突)传递给handleTransfer事件处理程序。此类型允许访问数据,例如事件的父事务及其参数。

每个实体必须具有唯一的ID,以避免与其他实体发生冲突。事件参数包含可以使用的唯一标识符是相当普遍的。 注意:使用事务哈希作为ID假定同一事务中没有其他事件创建使用该哈希作为ID的实体。

从存储加载实体 如果实体已经存在,则可以使用以下方法从存储中加载它:

let id = event.transaction.hash.toHex() // or however the ID is constructed
let transfer = Transfer.load(id)
if (transfer == null) {
  transfer = new Transfer(id)
}

// Use the Transfer entity as before

由于实体可能尚不存在于存储中,因此load方法返回的类型为Transfer 或者null。因此可能需要在使用该值之前检查是否为null。

注意:只有在映射实现时所做的更改取决于实体之前的数据时,才需要加载实体。有关更新现有实体的两种方式,请参见下一部分。

更新现有实体 有两种更新现有实体的方法:

加载实体,例如Transfer.load(id),在实体上设置属性,然后 .save() 将更新后的实体保存到存储。

只需使用new Transfer(id) 创建实体,在实体上设置属性,然后使用 .save() 将实体保存到存储中。如果实体已经存在,则更改将合并到其中。 在大多数情况下,由于生成了属性设置器(setter方法),因此更改属性很简单:

let transfer = new Transfer(id)
transfer.from = ...
transfer.to = ...
transfer.amount = ...

也可以使用以下两个指令之一来取消设置属性:

transfer.from.unset()
transfer.from = null

这仅适用于可选属性,即声明的属性中不带!的属性。

这仅适用于可选属性,即在 GraphQL 的声明的属性中不带!的属性。 这种属性的两个例子是:

owner:Bytes 或 amount:BigInt

更新数组属性要多花点精力,因为从实体获取数组会创建该数组的副本。这意味着在更改数组后必须再次显式设置数组属性。以下假设实体有一个数字数组字段:[BigInt!]! 字段。

// This won't work
entity.numbers.push(BigInt.fromI32(1))
entity.save()

// This will work
let numbers = entity.numbers
numbers.push(BigInt.fromI32(1))
entity.numbers = numbers
entity.save()

从存储中删除实体 当前无法通过生成的类型删除实体。相反,删除实体需要将实体类型的名称和实体ID传递给 store.remove来进行删除:

import { store } from '@graphprotocol/graph-ts'
...
let id = event.transaction.hash.toHex()
store.remove('Transfer', id)

以太坊API 以太坊API提供对智能合约、公共状态变量、合约函数、事件、交易和区块的访问。

支持以太坊类型 与实体一样,graph codegen 命令为子图中使用的所有智能合约和事件生成对应的类。为此,合同ABI必须成为子图声明清单中数据源的一部分。

通常,ABI 文件存储在 abis /文件夹中。 使用生成的类,以太坊类型和内置类型之间的转换在底层机制中进行,因此子图开发者不必关心。

以下示例说明了这一点。给定一个子图的schema定义,如

type Transfer @entity {
  from: Bytes!
  to: Bytes!
  amount: BigInt!
}

然后在以太坊上有一个 Transfer(address,address,uint256) 事件签名,三个参数: from 、to 和Amount ,类型分别为address、address和uint256类型。在数据处理中,address 和 uint256 ,会被转换为Address和BigInt,从而可以将它们传递给Transfer 实体的 Bytes! 和 BigInt! 属性:

let id = event.transaction.hash.toHex()
let transfer = new Transfer(id)
transfer.from = event.params.from
transfer.to = event.params.to
transfer.amount = event.params.amount
transfer.save()

事件、区块/交易数据

传递给事件处理程序的以太坊事件,例如前面示例中的 Transfer 事件,不仅提供了对事件参数的访问,还提供对其父事务及其所属的块的访问。可以从事件实例获得以下数据(这些类是graph-ts中以太坊模块的一部分):

class Event {
  address: Address
  logIndex: BigInt
  transactionLogIndex: BigInt
  logType: string | null
  block: Block
  transaction: Transaction
  parameters: Array<EventParam>
}

class Block {
  hash: Bytes
  parentHash: Bytes
  unclesHash: Bytes
  author: Address
  stateRoot: Bytes
  transactionsRoot: Bytes
  receiptsRoot: Bytes
  number: BigInt
  gasUsed: BigInt
  gasLimit: BigInt
  timestamp: BigInt
  difficulty: BigInt
  totalDifficulty: BigInt
  size: BigInt | null
}

class Transaction {
  hash: Bytes
  index: BigInt
  from: Address
  to: Address | null
  value: BigInt
  gasUsed: BigInt
  gasPrice: BigInt
  input: Bytes
}

访问智能合约状态 graph codegen 命令生成的代码还包括子图中使用的智能合约的类。这些可用于访问公共状态变量和在当前区块中调用智能合约其他方法的功能。 一种常见的模式是访问事件起源的智能合约。这可以通过以下代码实现:

// Import the generated contract class
import { ERC20Contract } from '../generated/ERC20Contract/ERC20Contract'
// Import the generated entity class
import { Transfer } from '../generated/schema'

export function handleTransfer(event: Transfer) {
  // Bind the contract to the address that emitted the event
  let contract = ERC20Contract.bind(event.address)

  // Access state variables and functions by calling them
  let erc20Symbol = contract.symbol()
}

以太坊上的 ERC20 智能合约具有一个叫 symbol 的公开的只读函数,就可以使用.symbol()进行调用。对于公共状态变量,将自动创建一个具有相同名称的方法。 子图的任何其他协定都可以从生成的代码中导入,并且可以绑定到有效地址。

记录和调试

import { log } from '@graphprotocol/graph-ts'

日志 API 允许子图将信息记录到 HyperGraph 节点的标准输出以及Graph 浏览器上。 可以使用不同的日志级别记录消息。提供了一种基本的格式字符串语法,以根据参数变量组成日志消息。

日志API包含以下功能: log.debug(fmt: string, args: Array): void——记录调试消息。 log.info(fmt: string, args: Array): void——记录提示类消息。 log.warning(fmt: string, args: Array): void——记录警告 log.error(fmt: string, args: Array): void——记录错误消息。 log.critical(fmt: string, args: Array): void——记录重要消息并终止子图。 日志 API 接受格式字符串和字符串值数组。然后,它将占位符替换为数组中的字符串值。第一个{}占位符被替换为数组中的第一个值,第二个{}占位符被替换为第二个值,依此类推。

log.info('Message to be displayed: {}, {}, {}', [
  value.toString(),
  anotherValue.toString(),
  'already a string',
])

记录一个或多个值 记录单个值

在下面的示例中,字符串值“ A”在被记录之前被传递到一个数组中成为['A']:

let myValue = 'A'

export function handleSomeEvent(event: SomeEvent): void {
  // Displays : "My value is: A"
  log.info('My value is: {}', [myValue])
}

从现有数组记录单个条目 在下面的示例中,尽管数组包含三个值,但仅记录了该参数数组的第一个值。

let myArray = ['A', 'B', 'C']

export function handleSomeEvent(event: SomeEvent): void {
  // Displays : "My value is: A"  (Even though three values are passed to `log.info`)
  log.info('My value is: {}', myArray)
}

从现有数组记录多个条目 arguments数组中的每个条目在日志消息字符串中都需要有自己的占位符{}。以下示例在日志消息中包含三个占位符{}。因此,将记录myArray中的所有三个值。

let myArray = ['A', 'B', 'C']

export function handleSomeEvent(event: SomeEvent): void {
  // Displays : "My first value is: A, second value is: B, third value is: C"
  log.info(
    'My first value is: {}, second value is: {}, third value is: {}',
    myArray,
  )
}

从现有数组记录特定条目 要在数组中显示特定值,必须提供索引值。

export function handleSomeEvent(event: SomeEvent): void {
  // Displays : "My third value is C"
  log.info('My third value is: {}', [myArray[2]])
}

记录事件信息 以下示例记录了事件的区块编号、区块哈希和事务哈希:

import { log } from '@graphprotocol/graph-ts'

export function handleSomeEvent(event: SomeEvent): void {
  log.debug('Block number: {}, block hash: {}, transaction hash: {}', [
    event.block.number.toString(), // "47596000"
    event.block.hash.toHexString(), // "0x..."
    event.transaction.hash.toHexString(), // "0x..."
  ])
}

IPFS API

import { ipfs } from '@graphprotocol/graph-ts'

智能合约有时会在区块链上锚定 IPFS 文件。这允许映射从智能合约中获取IPFS哈希,并从IPFS中读取相应的文件。文件数据将以 Bytes 数据格式的形式返回,通常需要进一步处理,例如使用本节稍后提到的JSON API。

给定一个IPFS哈希或路径,可以按照以下步骤从IPFS读取文件:

// Put this inside an event handler in the mapping
let hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'
let data = ipfs.cat(hash)

// Paths like `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`
// that include files in directories are also supported
let path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'
let data = ipfs.cat(path)

注意:ipfs.cat 目前尚未确定。如果在请求超时之前无法通过IPFS网络检索到文件,它将返回空值(null)。因此,始终检查结果是否为空(null)是很必要的。为了确保可以检索到文件,必须将它们关联到HyperGraph节点所对应到的IPFS节点。如果使用托管服务上,Heco 网络使用 https://f.hg.netwoprk/, BSC 网络使用 https://pf.hg.network。

也可以使用ipfs.map以流方式处理较大的文件。该函数需要IPFS文件的哈希或路径、回调的名称以及用于修改其行为的标志:

import { JSONValue, Value } from '@graphprotocol/graph-ts'

export function processItem(value: JSONValue, userData: Value): void {
  // See the JSONValue documentation for details on dealing
  // with JSON values
  let obj = value.toObject()
  let id = obj.get('id').toString()
  let title = obj.get('title').toString()

  // Callbacks can also created entities
  let newItem = new Item(id)
  item.title = title
  item.parent = userData.toString() // Set parent to "parentId"
  item.save()
}

// Put this inside an event handler in the mapping
ipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])

// Alternatively, use `ipfs.mapJSON`
ipfs.mapJSON('Qm...', 'processItem', Value.fromString('parentId'))

当前唯一支持的标志是json,必须将此标志传递给ipfs.map。使用json标志,IPFS文件必须包含一系列JSON值,每行一个值。调用ipfs.map将读取文件中的每一行,将其反序列化为JSONValue并为它们中的每一个值调用回调函数。然后,回调可以使用实体操作来存储来自JSONValue的数据。仅当调用ipfs.map的处理程序成功完成时才存储实体更改的内容。 同时,它们被保存在内存中,因此ipfs.map可以处理的文件大小受到限制。

成功后,ipfs.map将返回void。如果回调的任何调用导致错误,则调用ipfs.map的处理程序将中止,并将子图标记为失败。

加密API

import { crypto } from '@graphprotocol/graph-ts'

加密API使加密算法相关的函数可用于映射。目前只有一个:

  • crypto.keccak256(input: ByteArray): ByteArray

JSON API

import { json, JSONValueKind } from '@graphprotocol/graph-ts'

JSON数据可以使用 json API进行解析:

json.fromBytes(data:Bytes):JSONValue——解析来自Bytes数组的JSON数据 JSONValue 类提供了一种将值从任意JSON文档中提取出的方法。由于JSON值可以是布尔值,数字,数组等,因此JSONValue具有一种检查值类型的属性:

let value = json.fromBytes(...)
if (value.kind == JSONValueKind.BOOL) {
  ...
}

此外,还有一种方法可以检查该值是否为空:

value.isNull():boolean

当值的类型确定时,可以使用以下方法之一将其转换为内置类型:

value.toBool():boolean

value.toI64():i64

value.toF64():f64

value.toBigInt():BigInt

value.toString():string

value.toArray():Array ——(然后使用上述5种方法之一转换JSONValue)

类型转换参考

数据源元数据

您可以通过dataSource命名空间检查调用处理程序的数据源的智能合约地址,网络和上下文:

  • dataSource.address(): Address

  • dataSource.network(): string

  • dataSource.context(): DataSourceContext

实体和DataSourceContext 基础Entity类和子DataSourceContext类具有工具方法来动态设置和获取字段的能力:

  • setString(key: string, value: string): void

  • setI32(key: string, value: i32): void

  • setBigInt(key: string, value: BigInt): void

  • setBytes(key: string, value: Bytes): void

  • setBoolean(key: string, value: bool): void

  • setBigDecimal(key, value: BigDecimal): void

  • getString(key: string): string

  • getI32(key: string): i32

  • getBigInt(key: string): BigInt

  • getBytes(key: string): Bytes

  • getBoolean(key: string): boolean

  • getBigDecimal(key: string): BigDecimal

Last updated