shaneson.eth

Posted on Mar 10, 2022Read on Mirror.xyz

Subgraph 超快速入门开发简要

Subgraph其实非常简单。我这边大概就学了3个小时吧,就可以直接开发了。这里简单整理一下要点。

介绍

首先要知道它是做什么的。Subgraph是用来做链上数据索引的。熟悉区块链的底层的小伙伴肯定知道,假如要监听链上数据的事件/合约状态的话,我们一般是要写一个死循环,然后调用sdk来监听事件/合约,然后把数据存到数据库/处理。诸如此类的。但这很非常累。

我们很多的业务逻辑都是要监听链上数据然后进行分析的。基于此,Subgraph就诞生了。

核心--- 链上数据索引(自定义的链上数据库)

如果要快速入门subgraph,我觉得只需要理解到它是一个链上数据索引那么就没啥问题。直接可以看语法了。因为接下来的内容就和传统数据库的学习很相近了。

create subgraph == 创建数据库

创建图呢,有两种方法,法一:

graph init \
  --product subgraph-studio
  --from-contract <CONTRACT_ADDRESS> \
  [--network <ETHEREUM_NETWORK>] \
  [--abi <FILE>] \
  <SUBGRAPH_SLUG> [<DIRECTORY>]

法二:

graph init --studio <SUBGRAPH_SLUG>

The <SUBGRAPH_SLUG>is the ID of your subgraph in Subgraph Studio, it can be found on your subgraph details page.

修改subgraph配置 == 修改数据库配置

Subgraph的配置十分重要,但是方便的一点在于你几乎不用去做修改。graph-cli这个脚手架你可以基于你输入的合约地址,链的选择,项目名,合约名。去自动生成以下的文件。其中,最重要的应该是:eventHandlers,callHandlers和blockHandlers。这些是用来处理捕捉到事件/信号的时候用来处理数据的地方。

specVersion: 0.0.4
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.6
      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

Entity Relation == 数据库设计

这一步几乎是最重要的。是对schema的设计,其实本质上可以理解成数据库设计。主要分为几种:

一对一关系

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

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

一对多关系

type Token @entity {
  id: ID!
}

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

多对多关系

type Organization @entity {
  id: ID!
  name: String!
  members: [User!]!
}

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

Data Source == Web开发中的 DAO层(或者数据输入)

Emmm,这里本质就是当遇到callback的时候,要把什么数据写进graph上。这里就是具体逻辑具体分析了。我这里挑了abracadabra的一个MIM合约的Transfer函数做了监听,假如发生了Transfer,我就记下来。也是非常简单的。

import { Address, BigInt } from "@graphprotocol/graph-ts"
import {
  MagicInternetMoney,
  Approval,
  OwnershipTransferred,
  Transfer
} from "../generated/MagicInternetMoney/MagicInternetMoney"
import { MintRecord, MagicInternetMoneyEntity} from "../generated/schema"

export function handleTransfer(event: Transfer): void {
  const MagicInternetMoneyV1Instance = MagicInternetMoney.bind(event.address)

  const magicInternetMoneyID = event.address.toString()
  let magicInternetMoney = MagicInternetMoneyEntity.load(magicInternetMoneyID)
  let mintRecord = MintRecord.load(event.transaction.from.toHex())
  
  // update MagicInternetMoney
  if (!magicInternetMoney) {
    magicInternetMoney = new MagicInternetMoneyEntity(magicInternetMoneyID)
    magicInternetMoney.decimals = MagicInternetMoneyV1Instance.decimals()
    magicInternetMoney.totalSupply = MagicInternetMoneyV1Instance.totalSupply()
    magicInternetMoney.save()
  }

  if (!mintRecord) {
    mintRecord = new MintRecord(event.transaction.from.toHex())
    const _from = event.parameters[0].value.toAddress()
    const _to = event.parameters[1].value.toAddress()
    const _value = event.parameters[2].value.toBigInt()
    const _lastMint  = MagicInternetMoneyV1Instance.lastMint()

    mintRecord.tokenReceiver = _from
    mintRecord.tokenReceiver = _to
    mintRecord.amount = _value
    mintRecord.time = _lastMint.value0
    mintRecord.token = magicInternetMoney.id

    mintRecord.save()
  }

}


export function handleApproval(event: Approval): void {}

export function handleOwnershipTransferred(event: OwnershipTransferred): void {}

编译 + 部署

两行命令就可以发布到子网/托管网络,实现数据监听了。非常方便。

graph codegen && graph build
graph deploy --studio clink-demo