目次
TheGraphとは
TheGraphとはブロックチェーン上のデータをインデックス化するサービスです。
インデックス化とは、大量のデータを効率的に検索・アクセスできるように整理することを指し、本の目次をイメージしてもらうと分かりやすいでしょう。
ブロックチェーンのデータは誰もが見ることができますが、そのデータは時系列で記録されてインデックス化されていません。そのため、特定のキーワードやパラメータに基づいてデータを取得するのには手間がかかってしまいます。
しかし、TheGraphを使ってブロックチェーン上のデータをインデックス化することでデータを効率よく取得することができます。TheGraphの概要に関してはこちらの記事をご覧ください。
TheGraphでは、特定のスマートコントラクトとそのイベントを軸にデータをインデックス化することが可能で、どのようにインデックス化するかを定義しアクセスできるようにするものをSubgraphと呼びます。
本記事では、TheGraphの使い方について、実際にSubgraphを作成しながら解説します。
Subgraphのデプロイ方法
今回はTheGraphが提供するSubgraph作成のための開発環境である「studio」を利用します。
今回は、日本円に連動したステーブルコインであるJPYCのSubgraphをこちらのドキュメントにしたがって作成していきます。
Quick Start : https://thegraph.com/docs/en/quick-start/
JPYC : https://jpyc.jp/
Subgrapの作成は非常に簡単で下記のステップで行えます。
- Subgraph Studioでsubgraphを作成する
- GraphCLIをインストールする
- subgraphの自動生成
- subgraphのデプロイ
1. Subgraph Studioでsubgraphを作成する
Studioにアクセスし、「Connect Wallet」ボタンを押してブラウザにインストールされているwalletをサイトに接続し署名することでログインすることができます。Studioを利用するときはMetamaskなどのwalletが必要になるので事前にインストールしておいてください。
ログインできたら「Create a Subgraph」を押し、Subgraphの名前は「JPYC」、チェーンは「Polygon」を選んで作成してみます。
作成が完了すると、Subgraph専用のページが作成されます。ページの右側にSubgraph作成のためのステップが説明されているので、途中でわからなくなった場合はそちらも参考にしてみてください。
また、サイトにはDEPLOY KEYが記載されています。このDEPLOY KEYが他の人に漏れてしまうと、第三者が勝手にSubgrapを更新できてしまいます。関係者以外に共有しないようにしてください。
2. GraphCLIをインストールする
# NPM
$ npm install -g @graphprotocol/graph-cli
# Yarn
$ yarn global add @graphprotocol/graph-cli
3. subgraphの自動生成
Graph CLIのインストールが完成したら、CLIを利用してSubgraphを自動生成します。
下記のコマンドを実行し、適宜必要な情報を渡すだけで簡単にsubgraphを作成することができます。
SUBGRAPH_SLUG
は①のステップで作成したSubgraphの名前のことで、今回はjpycになります。
graph init --studio <SUBGRAPH_SLUG>
実際に実行したコマンドはこちらになります。
=> % graph init --studio jpyc ✔ Protocol · ethereum ✔ Subgraph slug · jpyc ✔ Directory to create the subgraph in · jpyc ✔ Ethereum network · matic ✔ Contract address · 0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB ✔ Fetching ABI from Etherscan ✖ Failed to fetch Start Block: Failed to fetch contract creation transaction hash ✔ Start Block · 28212531 ✔ Contract Name · JPYC ✔ Index contract events as entities (Y/n) · true Generate subgraph Write subgraph to directory ✔ Create subgraph scaffold ✔ Initialize networks config ✔ Initialize subgraph repository ✔ Install dependencies with yarn ✔ Generate ABI and schema types with yarn codegen Add another contract? (y/n): Subgraph jpyc created in jpyc
各項目の詳細はこちらになります。
- Protocol : Ethereumを選択します。今回はPolygonを利用したいので少々紛らわしいですが、EthereumはEVMと同義と捉えてください。
- Subgraph slug : Studioで作成したsubgraphの名前になります。
- Directory to create the subgraph in : ディレクトリの名前を記入します。今回はわかりやすいようにjpycにしました。
- Ethereum network : maticを選択します。TheGraphで利用されるネットワーク名と一般的に利用されるネットワーク名に違いがあるので注意してください。ネットワークリストはこちらにのっています。
- Contract address : インデックス化したいコントラクトアドレスを指定します。
- 今回使用するJPYCはアップフレーダブルコントラクトが使われており、Proxyのアドレスを指定してしまうとうまくコードが自動生成されません。したがって、コードの自動生成のためにImplementationのコントラクトアドレスを指定しました。少々わかりづらくなるため、ただTheGraphを試したい方はアップグレーダブルでないコントラクトを利用するのをお勧めします。
- 今回はJPYCのコントラクトはscanサービスで公開されているため、コントラクトアドレスを記載した後に「Fetching ABI from Etherscan」と出てきて自動的にABIが取得されました。公開されてないコントラクトを利用する場合は、ABIファイルを指定する必要があります。
- Start block : 該当スマートコントラクトがデプロイされたblockのblocknumberを記載します。scan系のサービスでコントラクトを検索すると、「at txn 0x…」でデプロイしたtxhashが表示されてるのでそこからご確認ください。
- Index contract events as entities (Y/n) : trueにすることでコントラクトに記載されているイベントをベースにSubgraphのコードを自動生成してくれます
コマンドの実行が完了するとファイルが下記のようなファイルが自動的に作成されています。
=> % tree jpyc -I 'node_modules' jpyc ├── abis │ └── JPYC.json ├── generated │ ├── JPYC │ │ └── JPYC.ts │ └── schema.ts ├── networks.json ├── package.json ├── schema.graphql ├── src │ └── jpyc.ts ├── subgraph.yaml ├── tests │ ├── jpyc-utils.ts │ └── jpyc.test.ts ├── tsconfig.json └── yarn.lock
自動生成されたsrc/
配下のコードはhandleXXX
という関数が複数あります。これらはXXX
のイベントが発火されると実行される関数になります。
実際に、schema.graphql
とsrc/jpyc.ts
を開くとこのようなコードになっており、JPYCのTransferのEventが発火されるとTransferのschemaにしたがってデータが保存されることになります。
4. subgraphのデプロイ
Subgraphが生成されたら下記コマンドでデプロイします。
* 今回、JPYCのImplementationのコントラクトアドレスでSubgraphを作成したので、subgraph.yaml
とnetwork.jsonに記載されているアドレスをProxyのアドレス(普段JPYCとして利用されているコントラクトアドレス)に変更します。アップグレーダブルコントラクトを利用してない場合はこちらの変更は不要です。
$ graph codegen && graph build ・・・・・・・ $ graph auth --studio <DEPLOY_KEY> ・・・・・・・ $ graph deploy --studio <SUBGRAPH_SLUG> ・・・・・・・ Deployed to https://thegraph.com/studio/subgraph/jpyc Subgraph endpoints: Queries (HTTP): https://api.studio.thegraph.com/query/54233/jpyc/v0.0.1 Deployed to https://thegraph.com/studio/subgraph/jpyc
デプロイコマンドを実行したら最後にQueries (HTTP)が出てきますが、このURLがSubgraphのAPIになります。作成したSubgraphの情報を取得してWebサイト等で表示したい場合はこのURLを利用します。
デプロイが実行されると、Studioのページが更新されSYNCINGのところにどれだけデータを取得できているかが表示されます。ここが100%になるとデータを取得できるようになります。
「Playground」を実際にSubgraphを試すことができ、添付画像では最新10件のTrnasferのEventを取得するクエリーの結果はこのようになります。
このように、スマートコントラクトに定義されているイベントだけであればCLIを使って簡単にインデックス化することができます。
Subgraphのカスタマイズ
CLIで自動生成されたSubgraphは編集可能です。今回は、1日あたりのJPYCの送信量と取引回数、アドレス別の送信回数のクエリを作成してみます。
Subgraphをカスタマイズする際は、主にschema.graphql
と AssemblyScript(src配下のファイル)
の2つのファイルを編集します。
schema.graphql
schemaとはデータベースの「設計図」のようなものです。データベースにはたくさんのデータが保存されるため、それらのデータがどのようなもので、どのように関連しているのかを示すことでデータを効率よく読み書きすることができます。
実際に「1日あたりのJPYCの送信量と取引回数」「合計の送信量」のschemaを作成すると下記のようになります。
type DailyVolume @entity { id: ID! date: Int! volume: BigInt! transactionCount: Int! }
dateは日付、volumeはその日の取引量、transactionCountはその日の取引回数を表しています。また!
は、空の値が入ってはいけないことを指しています。
これらの書き方はTheGraph特有のものではなく、GraphQLのものと同じなので詳細な書き方が気になる方はGraphQLのドキュメントを参考にしてください。
Schemas and Types : https://graphql.org/learn/schema/
このschemeを元にデータを保存していきます。
AssemblyScript(src配下のファイル)
AssemblyScript(SRCは以下のファイル)では、どのようにデータを取得して保存するかを定義したプログラムになります。CLIで初期化した際に、コントラクトで定義されたEventにであれば自動的にコートが実装されています。
例えば、TransferのEventであればこのようになっています。
export function handleTransfer(event: TransferEvent): void { let entity = newTransfer( event.transaction.hash.concatI32(event.logIndex.toI32()) ) entity.from=event.params.from entity.to=event.params.to entity.value=event.params.value entity.blockNumber=event.block.number entity.blockTimestamp=event.block.timestamp entity.transactionHash=event.transaction.hash entity.save() }
AssemblyScriptはTheGraph独自の書き方があるので少し解説します。
AssemblyScriptではEventの引数は、event.paramで取得することができます。JPYCではeventはevent Transfer(address indexed from, address indexed to, uint256 value);
と定義されており、コントラクトで定義されている名前と同じ名前を利用します。
また、AssemblyScriptでは該当Eventが発火されたトランザクションの内容も取得し保存することができます。例えば、event.block
では該当Eventが発火した際のblockの情報を取得することができ、event.transacton
では該当Eventが発火した際のtransactionの情報(例えばガス代など)を取得することができます。
どのようなデータを取れるかの詳細に関しては、AssemblyScript APIのページを参考にしてください。
https://thegraph.com/docs/en/developing/assemblyscript-api/#events-and-blocktransaction-data
では、実際にschemaで作成したデータの型にしたがってデータを保存していきます。
スクリプトはこのようになります。
function updateDailyVolume( blockTimestamp: BigInt, value : BigInt, ): void { // 1日の秒数86400なので、blockTimestampを86400で割ってtimestampの始まりの日時からの現在までの経過日数を計算し、その経過日数をデータのIDとしています // ロジックはUniswapから参照しました // https://github.com/graphprotocol/uniswap-subgraph/blob/ed19523cd80d29a6b403591f4f1b24746ab05023/src/mappings/exchange.ts#L190-L194 let timestamp = blockTimestamp.toI32(); let dayID = timestamp / 86400; let id = dayID.toString(); let dailyVolumeEntity = DailyVolume.load(id); // 該当日にTransferが行われていなかったら新しくデータを作成します if (dailyVolumeEntity == null) { dailyVolumeEntity = new DailyVolume(id); dailyVolumeEntity.volume = BigInt.fromString('0'); dailyVolumeEntity.transactionCount = 0; } // 該当日の2件目以降のtransferが発生した場合には、作成されてデータに加算していきます let dayStartTimestamp = dayID * 86400; dailyVolumeEntity.date = dayStartTimestamp dailyVolumeEntity.amount = dailyVolumeEntity.volume.plus(value) dailyVolumeEntity.transactionCount += 1 dailyVolumeEntity.save() }
スクリプトの修正が終わったらデプロイします。デプロイは「4. subgraphのデプロイ」で利用したコマンドで実行することができます。
graph codegen && graph build graph deploy --studio jpyc Which version label to use? (e.g. "v0.0.1"): v0.0.2
1回目のデプロイでv0.0.1を利用したので、今回はv0.0.2を指定しました。バージョン名が同じになるとデプロイできなくなるので、今まで利用したことがないバージョン名を使うように注意してください。
実際にdailyVolumeを取得してみるとこのようになります。
コードの全文に関してはこちらのディレクトリに置いています。
https://github.com/gaiax/BlockchainBiz/tree/main/20231006_TheGraph/jpyc
おわりに
本記事ではTheGraphでのSubgraphの作成の仕方、カスタマイズの方法について解説しました。
カスタマイズに関してはEventのparamのみを利用しましたが、AssemblyScriptではコントラクトのview関数を実行することができるためより複雑なことが可能です。
オンチェーンのデータは誰でも確認することができるものの、データがまとまっておらず、求めるデータを取得するのには手間がかかってしまいます。
TheGraphを利用することでかなり簡単にオンチェーンデータを取得できるのでぜひ使ってみてください!