ブロックチェーン企業で働くエンジニアのブログ

ブロックチェーン企業でエンジニアとして働いています。ブロックチェーンに限らずDXなどもメイン活動に

Ethereum上のイベントをGraphQLで取得するThe Graphの使い方

The GraphというSaaSを使うと、Ethereum上のイベントをGraphQLで取得することができます。3年ほど前に同じことをしようとしたら、せこせことノードにリクエストを投げていたのですが、今は非常に便利になって他サービスに乗っかることができるようになっています。

せこせこと投げた記事

www.blockchainengineer.tokyo

The Graph,実際に触ったので、誰かの役に立てばと備忘しておきます。The Graph概説はHashHubリサーチでも書かれているようです。(本文有料)

hashhub-research.com

The Graphとは

The GraphはEthereum上のイベントをGraphQLで取得することができるSaaSです。SaaSでありつつ、トークンを発行してエコシステムを回しています。

GraphにはSubGraphという、指定したコントラクトからイベントを抜き出すための定義マッピングを作成し、Graphのサーバーにデプロイします。クライアントサイドからGraphのサーバーにリクエストを投げることで、イベントのレスポンスを受け取れます。

The Graphでリクエストを投げるまでの流れ

The Graphでリクエストを投げるまでの流れは以下になります。

  1. コントラクトのデプロイ
  2. プロジェクト作成
  3. SubGraphの設定
  4. ABIからSubGraph,GraphQLのコード自動生成
  5. mapping.ts
  6. SubGraphデプロイ
  7. リクエス

今回はリクエスト準備までの1~6までそれぞれ説明していきます。なお、ブロックチェーンはRinkebyネットワークを利用します。ローカルのganacheではうまくいきませんでした。基本的には以下のチュートリアルに基づいて実行しています。

thegraph.com

環境 macOS Big Sur version 11.3
CPU: Apple M1 
node v15.14.0
Truffle v5.3.4 
yarn 1.22.10

コントラクトのデプロイ

この記事を読む人であれば、コントラクトデプロイはできると思うのでこちらは省略します。 なお僕はtruffleで、 truffle migrate でデプロイしました。

graphノードの起動

graphを落としてきて、dockerでノードを動かします。

$ git clone https://github.com/graphprotocol/graph-node/

$ cd graph-node/docker

$ ./setup.sh

$ docker compose up

docker-compose up に成功するとpostgres, ipfs, graphが起動します。

$ docker-compose up
Docker Compose is now in the Docker CLI, try `docker compose up`

Starting docker_postgres_1 ... done
Starting docker_ipfs_1     ... done
Recreating docker_graph-node_1 ... done
Attaching to docker_ipfs_1, docker_postgres_1, docker_graph-node_1
ipfs_1        | Changing user to ipfs
ipfs_1        | ipfs version 0.4.23
ipfs_1        | Found IPFS fs-repo at /data/ipfs
ipfs_1        | Initializing daemon...
ipfs_1        | go-ipfs version: 0.4.23-6ce9a35
ipfs_1        | Repo version: 7
ipfs_1        | System version: amd64/linux
ipfs_1        | Golang version: go1.12.16
postgres_1    | 
postgres_1    | PostgreSQL Database directory appears to contain a database; Skipping initialization
postgres_1    | 
ipfs_1        | Swarm listening on /ip4/127.0.0.1/tcp/4001

プロジェクト作成

Graphのプロジェクトを作成します。 は指定しなければ<SUBGRAPH_NAME>が設定されます。

# Yarn
$ yarn global add @graphprotocol/graph-cli
$ graph init --from-example <GITHUB_USERNAME>/<SUBGRAPH_NAME> <DIRECTORY>

成功すると以下のようにログが出ます。

$ graph init --from-example naomasabit/hogehoge hogehoge
✔ Cloning example subgraph
✔ Update subgraph name and commands in package.json
✔ Initialize subgraph repository
✔ Install dependencies with yarn
✔ Generate ABI and schema types with yarn codegen

Subgraph naomasabit/hogehoge created in hogehoge

Next steps:

  1. Run `graph auth https://api.thegraph.com/deploy/ <access-token>`
     to authenticate with the hosted service. You can get the access token from
     https://thegraph.com/explorer/dashboard/.

  2. Type `cd hogehoge` to enter the subgraph.

  3. Run `yarn deploy` to deploy the subgraph to
     https://thegraph.com/explorer/subgraph/naomasabit/hogehoge.

Make sure to visit the documentation on https://thegraph.com/docs/ for further information.
# hogehogeを確認
$ cd hogehoge
# テンプレートが色々作られている
$ ls
LICENSE     abis        contracts   migrations  package.json    src     truffle.js
README.md   bin     generated   node_modules    schema.graphql  subgraph.yaml   yarn.lock

SubGraphの設定

yamlの中は以下のようになっています。

  1. networkをrinkebyに変える
  2. addressをデプロイしたコントラクトアドレスに変える
  3. GraphQLのEntityを記載する
  4. abiのjsonに変える
  5. コントラクトのイベント名を設定する

が必要です。

$ less subgraph.yaml
specVersion: 0.0.2
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/example-subgraph
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Gravity
    network: mainnet # 1. rinkebyに変える
    source:
      address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC' # 2. デプロイしたコントラクトに変える
      abi: Gravity # コントラクトabiの名に変える
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      entities:
        - Gravatar # 3. GraphQLのEntityを記載する
      abis:
        - name: Gravity
          file: ./abis/Gravity.json # 4. abiのjsonに変える
      eventHandlers: # 5. コントラクトのイベント名と後述のmapping.tsで定義するhandlerメソッドをそれぞれ指定します。
        - event: NewGravatar(uint256,address,string,string) 
          handler: handleNewGravatar
        - event: UpdatedGravatar(uint256,address,string,string)
          handler: handleUpdatedGravatar
      file: ./src/mapping.ts

ABIからSubGraph,GraphQLのコード自動生成

ABIからSubGraphに使うGraphQLのコードを自動生成します。

% yarn codegen
yarn run v1.22.10
$ graph codegen
  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 specVersion from 0.0.1 to 0.0.2
✔ Apply migrations
⚠ Warnings while loading subgraph from subgraph.yaml: Warnings in subgraph.yaml:
  
    Path: repository
    The repository is still set to https://github.com/graphprotocol/example-subgraph.
    Please replace it with a link to your subgraph source code.
  
    Path: description
    The description is still the one from the example subgraph.
    Please update it to tell users more about your subgraph.

✔ Load subgraph from subgraph.yaml
  Load contract ABI from abis/Gravity.json
✔ Load contract ABIs
  Generate types for contract ABI: Gravity (abis/Gravity.json)
  Write types to generated/Gravity/Gravity.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

✨  Done in 2.42s.

そうすると、GeneratedディレクトリにGraphQLのコードができています。

generated % ls
Gravity     schema.ts
generated % ls Gravity 
Gravity.ts

schema.tsは以下のようになります。内容はサンプルabiですが、実際にはコントラクトのABIからイベントを取得するためのスキーマ定義が書かれます。

// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.

import {
  TypedMap,
  Entity,
  Value,
  ValueKind,
  store,
  Address,
  Bytes,
  BigInt,
  BigDecimal
} from "@graphprotocol/graph-ts";

export class Gravatar extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id !== null, "Cannot save Gravatar entity without an ID");
    assert(
      id.kind == ValueKind.STRING,
      "Cannot save Gravatar entity with non-string ID. " +
        'Considering using .toHex() to convert the "id" to a string.'
    );
    store.set("Gravatar", id.toString(), this);
  }

  static load(id: string): Gravatar | null {
    return store.get("Gravatar", id) as Gravatar | null;
  }

  get id(): string {
    let value = this.get("id");
    return value.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

mapping.tsの作成

mapping.tsを温かみのある手作業で作成します。前述した subgraph.yaml のeventHandlersにここに書かれるhandlerが設定されます。

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()
}

SubGraphデプロイ

Graphエクスプローラーのアカウントを作成し、マイページから Add Subgraph を生成すると、SubGraphの作成ができます。情報を入力して登録ます。

f:id:naomasabit:20210523201627p:plain

マイページにあるAuth Codeを用いて認証を行った後、yarn deploy をすると、SubGraphがデプロイできます。

$ yarn deploy

これでデプロイされ、疎通ができる状態になります。エンドポイントなどはマイページから取得します。

まとめ

イベントの取得を定期的に実行するのが非常に楽になったサービスです。Dappsを作成するときにこのサービスは非常に有用になると思います。なお、The Graphを試すにあたっては「試して学ぶスマートコントラクト開発」著者の篠原さんに色々と教えていただきました。感謝!

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

初めてのGraphQL ―Webサービスを作って学ぶ新世代API