現像 > サブグラフの作成

サブグラフの作成

サブグラフは、ブロックチェーンからデータを抽出し、加工して保存し、GraphQLで簡単にクエリできるようにします。

サブグラフの定義

サブグラフの定義は、いくつかのファイルで構成されています。

The Graphの分散型ネットワークでサブグラフを利用するためには、API キーを作成する必要がありますシグナルを追加し、少なくとも[10,000 GRT](/network-transition-faq/#how-can-i ensure-that-my-subgraph-will-pick-up-by-indexer-on-the-graph-network) をサブグラフに追加すると良いでしょう。

マニフェスト・ファイルの内容を詳しく説明する前に、サブグラフを構築してデプロイするために必要な Graph CLIをインストールする必要があります。

Graph CLI のインストール

このセクションへのリンク

Graph CLI は JavaScript で書かれており、使用するにはyarnまたは npmのいずれかをインストールする必要があります。

yarnをインストールしたら、次のコマンドを実行して Graph CLI をインストールする。

Install with yarn:

yarn global add @graphprotocol/graph-cli

Install with npm:

npm install -g @graphprotocol/graph-cli

インストールが完了すると、graph init コマンドを使用して、既存のコントラクトまたはサンプル サブグラフから新しいサブグラフ プロジェクトをセットアップできます。このコマンドを使用して、graph init --product subgraph-studio を渡すことにより、Subgraph Studio でサブグラフを作成できます。優先ネットワークに既にスマート コントラクトをデプロイしている場合、そのコントラクトから新しいサブグラフをブートストラップすることは、開始するのに適した方法です。

既存のコントラクトから

このセクションへのリンク

次のコマンドは、既存のコントラクトのすべてのイベントにインデックスを付けるサブグラフを作成します。Etherscan からコントラクト ABI をフェッチしようとしますが、ローカルファイルパスの要求にフォールバックします。オプションの引数のいずれかが欠けている場合は、対話形式で行われます。

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

<SUBGRAPH_SLUG>は、Subgraph Studio でのサブグラフの ID で、サブグラフの詳細ページに記載されています。

サブグラフの例から

このセクションへのリンク

graph initがサポートする 2 つ目のモードは、例となるサブグラフから新しいプロジェクトを作成することです。以下のコマンドがこれを行います:

graph init --studio <SUBGRAPH_SLUG>

例のサブグラフは、Dani Grant による Gravity コントラクトに基づいており、ユーザーのアバターを管理し、アバターが作成または更新されるたびに NewGravatar またはUpdateGravatarイベントを発行します。このサブグラフは、Gravatarエンティティを Graph Node ストアに書き込み、イベントに応じてこれらが更新されるようにすることで、これらのイベントを処理します。以下では、この例のサブグラフのマニフェストを構成するファイルについて説明します。

既存のサブグラフに新しいデータソースを追加する

このセクションへのリンク

v0.31.0 以降、graph-cliは、graph add コマンドにより既存のサブグラフに新しいデータソースを追加することをサポートしました。

graph add <address> [<subgraph-manifest default: "./subgraph.yaml">]
Options:
--abi <path> Path to the contract ABI (default: download from Etherscan)
--contract-name Name of the contract (default: Contract)
--merge-entities Whether to merge entities with the same name (default: false)
--network-file <path> Networks config file path (default: "./networks.json")

add コマンドは Etherscan から ABI を取得し (--abi オプションで ABI パスが指定されていない限り)、 graph init コマンドが dataSource --from-contract を作成したのと同じ方法で新しい dataSource を作成してスキーマとマッピングをそれに従って更新します。

--merge-entities オプションは、開発者が entityevent の名前の衝突をどのように処理したいかを指定します。

  • If true: the new dataSource should use existing eventHandlers & entities.
  • If false: a new entity & event handler should be created with ${dataSourceName}{EventName}.

契約書のaddressは、該当するネットワークのnetworks.jsonに書き込まれることになります

Note: 対話型CLIを使用している場合、graph initを正常に実行した後、新しいdataSourceを追加するよう促されます。

サブグラフ・マニフェスト

このセクションへのリンク

サブグラフ・マニフェストsubgraph.yamlは、サブグラフがインデックスするスマート・コントラクト、これらのコントラクトからのどのイベントに注目するか、そしてイベント・データをグラフ・ノードが保存するエンティティにどのようにマッピングするかを定義し、クエリを可能にします。サブグラフ・マニフェストの完全な仕様は、こちらをご覧ください。

例のサブグラフの場合、subgraph.yamlは次のようになっています:

specVersion: 0.0.4
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/graph-tooling
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
abi: Gravity
startBlock: 6175244
endBlock: 7175245
context:
foo:
type: Bool
data: true
bar:
type: String
data: 'bar'
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:
- handler: handleBlock
- handler: handleBlockWithCall
filter:
kind: call
file: ./src/mapping.ts

マニフェストを更新する重要な項目は以下の通りです:

  • description: a human-readable description of what the subgraph is. This description is displayed by the Graph Explorer when the subgraph is deployed to the hosted service.

  • repository: サブグラフのマニフェストが存在するリポジトリの URL です。これは、グラフエクスプローラでも表示されます。

  • features: 使用されるすべてのfeature名のリストです。

  • dataSources.source: サブグラフのソースとなるスマートコントラクトのアドレスと、使用するスマートコントラクトの abi です。アドレスはオプションで、省略すると、すべてのコントラクトからのマッチングイベントにインデックスを付けることができます。

  • dataSources.source.startBlock: データソースがインデックス作成を開始するブロックの番号(オプション)です。ほとんどの場合、コントラクトが作成されたブロックの使用をお勧めします。

  • dataSources.source.endBlock: The optional number of the block that the data source stops indexing at, including that block. Minimum spec version required: 0.0.9.

  • dataSources.context: key-value pairs that can be used within subgraph mappings. Supports various data types like Bool, String, Int, Int8, BigDecimal, Bytes, List, and BigInt. Each variable needs to specify its type and data. These context variables are then accessible in the mapping files, offering more configurable options for subgraph development.

  • dataSources.mapping.entities: データソースがストアに書き込むエンティティです。各エンティティのスキーマは、schema.graphql ファイルで定義されます。

  • dataSources.mapping.abis: ソースコントラクトおよびマッピング内から対話する他のスマートコントラクトのための 1 つまたは複数の名前付き ABI ファイルです。

  • dataSources.mapping.eventHandlers: このサブグラフが反応するスマートコントラクトイベントと、これらのイベントをストア内のエンティティに変換するマッピング内のハンドラ(例では./src/mapping.ts)をリストアップします。

  • dataSources.mapping.callHandlers: このサブグラフが反応するスマートコントラクト関数と、関数呼び出しの入力と出力をストア内のエンティティに変換するマッピング内のハンドラをリストアップします。

  • dataSources.mapping.blockHandlers: このサブグラフが反応するブロックと、ブロックがチェーンに追加されたときに実行されるマッピング内のハンドラーをリストします。フィルターを使用しない場合、ブロック ハンドラーはすべてのブロックで実行されます。オプションの call-filter は、filter フィールドと kind: call をハンドラーに追加することで提供できます。これは、ブロックにデータ ソース コントラクトへの呼び出しが少なくとも 1 つ含まれている場合にのみ、ハンドラーを実行します。

単一のサブグラフは複数のスマートコントラクトからデータを索引化できます。dataSources配列に、索引化するデータが必要な各コントラクトのエントリを追加してください

ブロック内のデータソースのトリガーは、以下のプロセスを使用して順序付けられます:

  1. イベントとコールのトリガーは、ブロック内のトランザクションインデックスで最初に並べられます。
  2. 同じトランザクション内のイベントトリガーとコールトリガーは、マニフェストで定義されている順序にしたがって、イベントトリガーが先、コールトリガーが後という規則で並べられます。
  3. ブロックトリガーは、イベントトリガーとコールトリガーの後に、マニフェストで定義されている順番で実行されます。

これらの順序規則は変更されることがあります。

ABI ファイルは、契約内容と一致している必要があります。ABI ファイルを入手するにはいくつかの方法があります:

  • 自分のプロジェクトを構築している場合は、最新の ABI にアクセスできる可能性があります。
  • 公開プロジェクトのサブグラフを作成している場合は、そのプロジェクトをコンピュータにダウンロードし、 truffle compileまたは solc to compile を使用して ABI を取得することができます。
  • ABI はEtherscanにもありますが、アップロードされた ABI が古いかもしれないので、必ずしも信頼できるものではありません。正しい ABI でないと、サブグラフの実行に失敗します。

サブグラフのスキーマは、schema.graphqlというファイルにあります。GraphQL スキーマは、GraphQL インターフェース定義言語を用いて定義される。GraphQL スキーマを書いたことがない場合は、GraphQL の型システムについての入門書をご覧になることをお勧めします。GraphQL スキーマのリファレンスドキュメントは、GraphQL APIのセクションにあります。

エンティティの定義

このセクションへのリンク

エンティティを定義する前に、一歩下がって、データがどのように構造化され、リンクされているかを考えることが重要です。すべてのクエリは、サブグラフのスキーマで定義されたデータモデルと、サブグラフでインデックス化されたエンティティに対して行われます。このため、Dap のニーズに合わせてサブグラフ・スキーマを定義すると良いでしょう。エンティティは、イベントや関数ではなく、「データを含むオブジェクト」と考えるとよいでしょう。

The Graphでは、schema.graphqlにエンティティタイプを定義するだけで、Graph Nodeがそのエンティティタイプのシングルインスタンスやコレクションを問い合わせるためのトップレベルのフィールドを生成してくれます。エンティティになるべき各タイプは、@entityディレクティブでアノテーションされることが要求されます。デフォルトでは、エンティティはミュータブルです。つまり、マッピングは既存のエンティティをロードし、それを変更し、そのエンティティの新しいバージョンを保存することができます。Mutable には代償があり、例えば、チェーンからそのまま抽出されたデータを含むなど、決して変更されないことが分かっているエンティティタイプには、@entity(immutable: true) で immutable としてマークすることが推奨されます。マッピングは、エンティティが作成されたのと同じブロック内で変更が行われる限り、Immutableエンティティに変更を加えることができます。Immutableなエンティティは、書き込みや問い合わせが非常に高速になるため、可能な限り使用すべきです。

以下のGravatarエンティティは、Gravatar オブジェクトを中心に構成されており、エンティティを定義する上での良い例です。

type Gravatar @entity(immutable: true) {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
accepted: Boolean
}

GravatarAcceptedエンティティとGravatarDeclinedエンティティの例は、イベントに基づいています。イベントや関数の呼び出しとエンティティを 1:1 で対応させることはお勧めできません。

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

任意フィールドと必須フィールド

このセクションへのリンク

エンティティのフィールドは、必須またはオプションとして定義できます。必須フィールドは、スキーマの中で !で示されます。マッピングで必須フィールドが設定されていない場合、フィールドを照会すると次のようなエラーが表示されます:

Null 以外のフィールド 'name' の null 値が解決されました

各エンティティには id フィールドが必要です。このフィールドは Bytes! または String! 型である必要があります。 Bytes! の ID を持つエンティティは書き込みが高速になるため、ID に人間が読み取れるテキストが含まれていない限り、通常は Bytes! を使用することをお勧めします。 String! id を持つものとしてクエリします。 id フィールドは主キーとして機能し、同じタイプのすべてのエンティティ間で一意である必要があります。歴史的な理由から、タイプ ID! も受け入れられ、String! と同義です。

例えば、let id = left.id.concat(right.id)leftright のidからidを生成するために使用されます。同様に、既存のエンティティのidとカウンタcountからidを構成するには、let id = left.id.concatI32(count) を使うことができます。この連結は、leftの長さが、例えば、left.idAddressであるように、全てのそうした実体に対して同じである限り、ユニークなidを作り出すことが保証されています。

組み込みの Scalar タイプ

このセクションへのリンク

GraphQL がサポートする Scalar

このセクションへのリンク

GraphQL API では、以下の Scalar をサポートしています:

タイプ説明書き
BytesByte 配列で、16 進数の文字列で表されます。Ethereum のハッシュやアドレスによく使われます。
Stringstring値の Scalar であり、Null 文字はサポートされておらず、自動的に削除されます。
Booleanboolean値を表す Scalar。
IntInt GraphQL の仕様では、Intのサイズは 32 バイトと定義されています。
Int8An 8-byte signed integer, also known as a 64-bit signed integer, can store values in the range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. Prefer using this to represent i64 from ethereum.
BigInt大きな整数。Ethereum のuint32, int64, uint64, ..., uint256 タイプに使用されます。注: int32, uint24 int8などuint32以下のものはi32として表現されます。
BigDecimalBigDecimalは、高精度の 10 進数を記号と指数で表します。指数の範囲は -6143 ~ +6144 です。有効数字 34 桁にまとめられます。

スキーマ内に enums を作成することもできます。enums は次のような構文になっています:

enum TokenStatus {
OriginalOwner
SecondOwner
ThirdOwner
}

スキーマで enum が定義されると、enum 値の文字列表現を使用してエンティティに enum フィールドを設定することができます。例えば、tokenStatusSecondOwnerに設定するには、まずエンティティを定義し、続いてentity.tokenStatus = "SecondOwnerでフィールドを設定します。以下の例は、Token エンティティが enum フィールドを持つように見えることを示しています:

enums の記述についての詳細は、GraphQL documentationを参照してください。

エンティティのリレーションシップ

このセクションへのリンク

エンティティは、スキーマ内の 1 つ以上の他のエンティティとリレーションシップを持つことができます。これらの関係は、クエリの中で走査されることがあります。The Graph のリレーションシップは単方向です。リレーションシップのどちらかの "端 "に単方向のリレーションシップを定義することで、双方向のリレーションシップをシミュレートすることができます。

リレーションシップは、指定されたタイプが他のエンティティのものであることを除けば、他のフィールドと同様にエンティティに定義されます。

1 対 1 のリレーションシップ

このセクションへのリンク

TransactionReceiptエンティティタイプとの 1 対 1 の関係を持つTransactionエンティティタイプを定義します:

type Transaction @entity(immutable: true) {
id: Bytes!
transactionReceipt: TransactionReceipt
}
type TransactionReceipt @entity(immutable: true) {
id: Bytes!
transaction: Transaction
}

1 対多のリレーションシップ

このセクションへのリンク

TokenBalanceエンティティタイプに、Token エンティティタイプとの 1 対多の関係が必要なものを定義します:

type Token @entity(immutable: true) {
id: Bytes!
}
type TokenBalance @entity {
id: Bytes!
amount: Int!
token: Token!
}

逆引き(Reverse lookups)

このセクションへのリンク

逆引きは、@derivedFromフィールドを使ってエンティティに定義できます。これにより、エンティティ上に仮想的なフィールドが作成されます。このフィールドにはクエリをかけることができますが、マッピング API を通じて手動で設定することはできません。むしろ、他のエンティティで定義された関係から派生します。このような関係では、関係の両側を保存することに意味があることはほとんどありません。一方の側だけを保存し、もう一方の側を派生させた方が、インデックス作成とクエリのパフォーマンスの両方が向上します。

1 対多の関係では、関係は常に「1」側に格納され、「多」側は常に派生されるべきです。「多」側にエンティティの配列を格納するのではなく、このように関係を格納することで、サブグラフのインデックス作成と問い合わせの両方で劇的にパフォーマンスが向上します。一般的に、エンティティの配列を保存することは、現実的に可能な限り避けるべきです。

tokenBalancesフィールドを派生させることで、トークンの残高にアクセスできるようになります:

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

多対多のリレーションシップ

このセクションへのリンク

ユーザーがそれぞれ任意の数の組織に所属しているような多対多の関係の場合、関係をモデル化する最も簡単な方法は、関係する 2 つのエンティティのそれぞれに配列として格納することですが、一般的には最もパフォーマンスの高い方法ではありません。対称的な関係であれば、関係の片側のみを保存する必要があり、もう片側は派生させることができます。

UserエンティティタイプからOrganizationエンティティタイプへの逆引きを定義します。以下の例では、Organizationエンティティの中からmembers属性を検索することで実現しています。クエリでは、Userorganizationsフィールドは、ユーザの ID を含むすべてのOrganizationエンティティを見つけることで解決されます。

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

この関係を保存するためのより効率的な方法は、次のようなスキーマを持つ User/ Organizationのペアごとに 1 つのエントリを持つマッピングテーブルを使用することです。

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

このアプローチでは、例えばユーザーの組織を取得するために、クエリをさらに 1 つのレベルに下げる必要があります:

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!
}

フルテキスト検索フィールド(Full Text Search)の定義

このセクションへのリンク

フルテキスト検索クエリは、テキスト検索入力に基づいてエンティティをフィルタリングし、ランク付けします。フルテキストクエリは、インデックス化されたテキストデータと比較する前に、クエリテキストの入力をステム処理することで、類似した単語のマッチを返すことができます。

フルテキストクエリの定義には、クエリ名、テキストフィールドの処理に使用される言語辞書、結果の順序付けに使用されるランキングアルゴリズム、および検索に含まれるフィールドが含まれます。各フルテキスト・クエリは複数のフィールドにまたがることができますが、含まれるフィールドはすべて単一のエンティティ・タイプのものでなければなりません。

フルテキストクエリを追加するには、GraphQL スキーマにフルテキスト指示文を含む_Schema_タイプを記述します。

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

例のbandSearchフィールドは、namedescriptionbioフィールドのテキスト文書に基づいてBandエンティティをフィルタリングするクエリで使用できます。全文検索 API の説明や詳しい使用例については、GraphQL API - Queriesを参照してください。

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

Feature Management:specVersion 0.0.4以降では、subgraph manifest のfeaturesセクションでfullTextSearchを宣言する必要があります。

異なる言語を選択すると、フルテキスト検索 API に決定的な影響を与えますが、場合によっては微妙な影響もあります。フルテキストクエリフィールドでカバーされるフィールドは、選択された言語のコンテキストで検査されるため、分析や検索クエリで生成される語彙は言語ごとに異なります。たとえば、サポートされているトルコ語辞書を使用した場合、"token "は "toke "にステム処理されますが、もちろん英語辞書では "token "にステム処理されます。

サポートされている言語の辞書:

コード辞書
simpleGeneral
daDanish
nlDutch
enEnglish
fiFinnish
frFrench
deGerman
huHungarian
itItalian
noNorwegian
ptポルトガル語
roRomanian
ruRussian
esSpanish
svSwedish
trTurkish

ランキングアルゴリズム

このセクションへのリンク

サポートされている結果の順序付けのアルゴリズム:

アルゴリズム説明書き
rankフルテキストクエリのマッチ品質 (0-1) を使用して結果を並べ替えます。
proximityRankProximityRank rank に似ていますが、マッチの近接性も含みます。

マッピングの記述

このセクションへのリンク

マッピングは、特定のソースからデータを取得し、スキーマ内で定義されているエンティティに変換します。マッピングは、WASM (WebAssembly) にコンパイルできる AssemblyScript と呼ばれる TypeScript のサブセットで記述されます。 AssemblyScript は通常の TypeScript よりも厳密ですが、使い慣れた構文を提供します

subgraph.yamlmapping.eventHandlersで定義されている各イベントハンドラに対して、同じ名前のエクスポートされた関数を作成します。各ハンドラーは、処理されるeventの名前に対応するタイプの event という 1 つのパラメータを受け入れる必要があります。

例題のサブグラフでは、src/mapping.tsNewGravatarUpdatedGravatar イベントのハンドラが含まれています:

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)
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
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イベントを受け取り、new Gravatar(event.params.id.toHex())で新しいGravatarエンティティを作成し、対応するイベント・パラメータを使ってエンティティ・フィールドを入力します。このエンティティのインスタンスは、event.params.id.toHex()の id 値を持つ変数gravatarで表されます。

2 番目のハンドラは、既存の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 Libraryは、Graph Node Store とのやりとりや、スマートコントラクトのデータやエンティティを扱うための便利な機能を備えています。mapping.ts@graphprotocol/graph-ts をインポートすることで、マッピングでこのライブラリを使用することができます。

スマートコントラクト、イベント、エンティティを簡単かつタイプセーフに扱うために、Graph CLIはサブグラフのGraphQLスキーマとデータソースに含まれるコントラクトABIからAssemblyScriptタイプを生成することができます。

これを行うためには

graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]
で行うことができます。

しかし、ほとんどの場合、package.jsonによってサブグラフがあらかじめ設定されているので、以下のいずれかを実行するだけで同じことが実現できます:

# Yarn
yarn codegen
# NPM
npm run codegen

これにより、subgraph.yaml で言及されている ABI ファイル内のすべてのスマート コントラクトに対して AssemblyScript クラスが生成され、これらのコントラクトをマッピング内の特定のアドレスにバインドし、ブロックに対して読み取り専用のコントラクト メソッドを呼び出すことができます。処理されます。また、すべてのコントラクト イベントのクラスを生成して、イベント パラメータ、およびイベントの発生元のブロックとトランザクションに簡単にアクセスできるようにします。これらの型はすべて <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 スキーマのエンティティタイプごとに 1 つのクラスが生成される。これらのクラスは、タイプセーフなエンティティのロード、エンティティ・フィールドへのリード・ライト・アクセスのほか、エンティティをストアに書き込むためのsave()メソッドを提供する。すべてのエンティティ・クラスは<OUTPUT_DIR>/schema.tsに書き込まれ、マッパーは以下のようにしてインポートすることができます。

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

注: GraphQL スキーマやマニフェストに含まれる ABI を変更するたびに、コード生成を再実行する必要があります。また、サブグラフをビルドまたはディプロイする前に、少なくとも一度は実行する必要があります。

コード生成では、src/mapping.tsのマッピングコードはチェックされません。サブグラフをグラフ・エクスプローラーに配置する前にチェックしたい場合は、yarn buildを実行して、TypeScript コンパイラが検出する可能性のある構文エラーを修正することができます。

データソーステンプレート

このセクションへのリンク

EVM 互換のスマート コントラクトの一般的なパターンは、レジストリ コントラクトまたはファクトリ コントラクトの使用です。1 つのコントラクトが、それぞれ独自の状態とイベントを持つ任意の数の他のコントラクトを作成、管理、または参照します。

これらのサブコントラクトのアドレスは、事前にわかっている場合とわかっていない場合があり、これらのコントラクトの多くは、時間の経過とともに作成および/または追加される可能性があります。このような場合、単一のデータ ソースまたは固定数のデータ ソースを定義することは不可能であり、より動的なアプローチ、つまり データ ソース テンプレートが必要とされるのはこのためです。

メインコントラクトのデータソース

このセクションへのリンク

まず、メインコントラクトの通常のデータソースを定義します。下のスニペットは Uniswap exchange factory contract のデータソースの例を簡略化して示しています。NewExchange(address,address)イベントハンドラに注目してください。これはファクトリーコントラクトによってチェーン上に新しいエクスチェンジコントラクトが作成された際に発行されます。

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

動的に作成されるコントラクトのデータソーステンプレート

このセクションへのリンク

次に、data source templatesをマニフェストに追加します。データソース テンプレートは、sourceの下に定義済みのコントラクト アドレスがないことを除けば、通常のデータソースと同じです。一般的には、親コントラクトが管理または参照するサブコントラクトのタイプごとに 1 つのテンプレートを定義することになります。

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.6
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

データソーステンプレートのインスタンス化

このセクションへのリンク

最後のステップでは、メインのコントラクト マッピングを更新して、テンプレートの 1 つからダイナミック データ ソース インスタンスを作成します。この例では、メインのコントラクトマッピングを変更してExchangeテンプレートをインポートし、Exchange.create(address)メソッドを呼び出して新しい Exchange コントラクトのインデックス作成を開始します。

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')

すべての値の型に対して、setStringgetStringのようなセッターやゲッターがあります。

スタートブロック(start Blocks)

このセクションへのリンク

startBlockはオプションの設定で、データソースがチェーンのどのブロックからインデックス作成を開始するかを定義できます。開始ブロックを設定することで、データソースは無関係な何百万ものブロックをスキップすることができます。通常、サブグラフの開発者はstartBlockをデータソースのスマートコントラクトが作成されたブロックに設定します。

dataSources:
- kind: ethereum/contract
name: ExampleSource
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: ExampleContract
startBlock: 6627917
mapping:
kind: ethereum/events
apiVersion: 0.0.6
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.Callを引数として受け取ります。トランザクションのコールチェーンのどの深さで行われたコールでもマッピングがトリガーされ、プロキシコントラクトを介したデータソースコントラクトとのアクティビティをキャプチャすることができます。

コールハンドラーは、次の 2 つのケースのいずれかでのみトリガされます:指定された関数がコントラクト自身以外のアカウントから呼び出された場合、または Solidity で外部としてマークされ、同じコントラクト内の別の関数の一部として呼び出された場合。

Note: コールハンドラは現在、ParityトレースAPIに依存しています。BNB chainやArbitrumのような特定のネットワークは、このAPIをサポートしていません。これらのネットワークのインデックスを持つサブグラフが1つ以上のコールハンドラを含む場合、同期を開始しません。サブグラフの開発者は、代わりにイベントハンドラを使用する必要があります。イベント・ハンドラはコール・ハンドラよりもはるかに高性能であり、すべてのevmネットワークでサポートされています。

コールハンドラーの定義

このセクションへのリンク

マニフェストにコール ハンドラを定義するには、購読したいデータ ソースの下に callHandlers配列を追加します。

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

functionは、コールをフィルタリングするための正規化された関数シグネチャです。handlerプロパティは、ターゲット関数がデータソースコントラクトで呼び出されたときに実行したい、マッピング内の関数の名前です。

各コールハンドラは、呼び出された関数の名前に対応するタイプを持つ 1 つのパラメータを取ります。上のサブグラフの例では、マッピングはcreateGravatar 関数が呼び出されたときのハンドラを含み、引数としてCreateGravatarCallパラメータを受け取ります:

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

handleCreateGravatar関数は、@graphprotocol/graph-tsが提供するethereum.CallのサブクラスであるCreateGravatarCallを新たに受け取り、コールの型付けされた入出力を含みます。CreateGravatarCallのタイプは、graph codegenを実行したときに生成されます。

ブロック・ハンドラー

このセクションへのリンク

コントラクトイベントやファンクションコールの購読に加えて、サブグラフは、新しいブロックがチェーンに追加されると、そのデータを更新したい場合があります。これを実現するために、サブグラフは各ブロックの後、あるいは事前に定義されたフィルタにマッチしたブロックの後に、関数を実行することができます。

filter:
kind: call

定義されたハンドラーは、ハンドラーが定義されているコントラクト(データソース)への呼び出しを含むすべてのブロックに対して一度だけ呼ばれます。

Note: コールハンドラは現在、ParityトレースAPIに依存しています。BNB chainやArbitrumのような特定のネットワークは、このAPIをサポートしていません。これらのネットワークのインデックスを持つサブグラフが1つ以上のコールハンドラを含む場合、同期を開始しません。サブグラフの開発者は、代わりにイベントハンドラを使用する必要があります。イベント・ハンドラはコール・ハンドラよりもはるかに高性能であり、すべてのevmネットワークでサポートされています。

ブロックハンドラーにフィルターがない場合、ハンドラーはブロックごとに呼び出されます。1 つのデータソースには、各フィルタータイプに対して 1 つのブロックハンドラーしか含めることができません。

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

Requires specVersion >= 0.0.8

Note: Polling filters are only available on dataSources of kind: ethereum.

blockHandlers:
- handler: handleBlock
filter:
kind: polling
every: 10

The defined handler will be called once for every n blocks, where n is the value provided in the every field. This configuration allows the subgraph to perform specific operations at regular block intervals.

Requires specVersion >= 0.0.8

Note: Once filters are only available on dataSources of kind: ethereum.

blockHandlers:
- handler: handleOnce
filter:
kind: once

The defined handler with the once filter will be called only once before all other handlers run. This configuration allows the subgraph to use the handler as an initialization handler, performing specific tasks at the start of indexing.

export function handleOnce(block: ethereum.Block): void {
let data = new InitialData(Bytes.fromUTF8('initial'))
data.data = 'Setup data here'
data.save()
}

マッピング関数は、唯一の引数としてethereum.Blockを受け取ります。イベント用のマッピング関数と同様に、この関数はストア内の既存のサブグラフエンティティにアクセスしたり、スマートコントラクトを呼び出したり、エンティティを作成または更新したりすることができます。

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

Solidity で匿名イベントを処理する必要がある場合は、例のようにイベントのトピック 0 を提供することで実現できます:

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

シグネチャと topic0 の両方が一致した場合にのみ、イベントが発生します。デフォルトでは、topic0はイベントシグネチャのハッシュと同じです。

イベントハンドラにおけるトランザクションレシーブ

このセクションへのリンク

specVersion 0.0.5 および apiVersion 0.0.7 以降、イベント ハンドラーは、それらを発行したトランザクション

これを行うには、イベント ハンドラをサブグラフ マニフェストで新しい receipt: true キー (オプション、デフォルトは false) を使用して宣言する必要があります。

eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
receipt: true

ハンドラ関数の内部では、レシートは Event.receipt フィールドでアクセスすることができます。receipt キーが false に設定されているか、マニフェストで省略されている場合、代わりに null 値が返されることになります。

specVersion 0.0.4以降、サブグラフ機能はマニフェストファイルのトップレベルにあるfeaturesセクションで、以下の表のようにcamelCase の名前を使って明示的に宣言する必要があります:

特徴名前
致命的でないエラーnonFatalErrors
Full-text SearchfullTextSearch
Graftinggrafting
IPFS on Ethereum ContractsipfsOnEthereumContracts or nonDeterministicIpfs

例えば、サブグラフがFull-Text SearchNon-fatal Errorsの機能を使用する場合、マニフェストのfeaturesフィールドは次のようになります:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- fullTextSearch
- nonFatalErrors
dataSources: ...

宣言せずに機能を使用すると、サブグラフの展開時にvalidation errorが発生しますが、機能を宣言しても使用しなければエラーは発生しないことに注意してください。

イーサリアム コントラクトの IPFS

このセクションへのリンク

IPFS と Ethereum を組み合わせる一般的なユースケースは、チェーン上で維持するにはコストがかかりすぎるデータを IPFS 上に保存し、Ethereum コントラクトで IPFS ハッシュを参照することです。

このような IPFS のハッシュが与えられた場合、サブグラフはipfs.catipfs.mapを使って IPFS から対応するファイルを読み取ることができます。ただし、これを確実に行うためには、サブグラフのインデックスを作成するグラフノードが接続する IPFS ノードに、これらのファイルがピン留めされている必要があります。hosted serviceの場合、これは/

注: グラフ ネットワークはまだ ipfs.catipfs.map をサポートしていないため、開発者はデプロイしないでくださいStudio を介してネットワークへのその機能を使用するサブグラフ

機能管理: ipfsOnEthereumContracts はサブグラフ・マニフェストの features で宣言されなければなりません。非 EVM チェーンについては、nonDeterministicIpfs エイリアスも同じ目的で使用できます。

ローカルのグラフノードを実行する場合、この実験的な機能を使用してサブグラフのインデックスを作成するために、GRAPH_ALLOW_NON_DETERMINISTIC_IPFS環境変数が設定されている必要があります。

致命的でないエラー

このセクションへのリンク

すでに同期しているサブグラフのインデックスエラーは、デフォルトではサブグラフを失敗させ、同期を停止させます。サブグラフは、エラーが発生したハンドラーによる変更を無視することで、エラーが発生しても同期を継続するように設定することができます。これにより、サブグラフの作成者はサブグラフを修正する時間を得ることができ、一方でクエリは最新のブロックに対して提供され続けますが、エラーの原因となったバグのために結果が一貫していない可能性があります。なお、エラーの中には常に致命的なものもあり、致命的でないものにするためには、そのエラーが決定論的であることがわかっていなければなりません。

注: グラフ ネットワークはまだ致命的ではないエラーをサポートしていないため、開発者はその機能を使用するサブグラフを Studio 経由でネットワークにデプロイしないでください。

非致命的エラーを有効にするには、サブグラフのマニフェストに以下の機能フラグを設定する必要があります:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- fullTextSearch
...

クエリは、subgraphError引数を通じて、潜在的な不整合を持つデータのクエリをオプトインする必要があります。また、例のように、サブグラフがエラーをスキップしたかどうかを確認するために、_metaをクエリすることも推奨されます:

foos(first: 100, subgraphError: allow) {
id
}
_meta {
hasIndexingErrors
}

サブグラフにエラーが発生した場合、そのクエリはデータと、"indexing_error"というメッセージを持つ graphql のエラーの両方を返します(以下のレスポンス例):

"data": {
"foos": [
{
"id": "0xdead"
}
],
"_meta": {
"hasIndexingErrors": true
}
},
"errors": [
{
"message": "indexing_error"
}
]

既存のサブグラフへのグラフト

このセクションへのリンク

Note: it is not recommended to use grafting when initially upgrading to The Graph Network. Learn more here.

サブグラフが最初にデプロイされると、対応するチェーンのジェネシス ブロック (または各データ ソースで定義された startBlock) でイベントのインデックス作成が開始されます。既存のサブグラフのデータを再利用し、かなり後のブロックからインデックス作成を開始することは有益です。このインデックス作成モードは グラフティング と呼ばれます。失敗した既存のサブグラフを迅速に、または一時的に再び機能させることができます。

subgraph.yamlのサブグラフマニフェストのトップレベルにgraftブロックがある場合、サブグラフはベースサブグラフにグラフトされます:

description: ...
graft:
base: Qm... # Subgraph ID of base subgraph
block: 7345624 # Block number

マニフェストにgraftブロックが含まれるサブグラフがデプロイされると、グラフノードは base サブグラフのデータを、指定されたブロックまでコピーし、そのブロック以降の新しいサブグラフのインデックスを作成し続ける。ベースサブグラフは、対象となるグラフノードのインスタンス上に存在し、少なくとも与えられたblockまでのインデックスを持っている必要があります。このような制限があるため、グラフト化は開発時や緊急時に、グラフト化されていない同等のサブグラフの生成を早めるためにのみ使用するべきです。

グラフトはベースデータのインデックスではなくコピーを行うため、スクラッチからインデックスを作成するよりもサブグラフを目的のブロックに早く到達させることができますが、非常に大きなサブグラフの場合は最初のデータコピーに数時間かかることもあります。グラフトされたサブグラフが初期化されている間、グラフノードは既にコピーされたエンティティタイプに関する情報を記録します。

グラフト化されたサブグラフは、ベースとなるサブグラフのスキーマと同一ではなく、単に互換性のある GraphQL スキーマを使用することができます。また、それ自体は有効なサブグラフのスキーマでなければなりませんが、以下の方法でベースサブグラフのスキーマから逸脱することができます。

  • エンティティタイプを追加または削除する
  • エンティティタイプから属性を削除する
  • エンティティタイプに nullable 属性を追加する
  • null 化できない属性を null 化できる属性に変更する
  • enums に値を追加する
  • インターフェースの追加または削除
  • インターフェースがどのエンティティタイプに実装されるかを変更する

Feature Management:graftingはサブグラフマニフェストのfeaturesの下で宣言しなければなりません。

ファイルデータソース

このセクションへのリンク

ファイルデータソースは、堅牢で拡張可能な方法でインデックス作成中にオフチェーンデータにアクセスするための新しいサブグラフ機能です。ファイルデータソースは、IPFS および Arweave からのファイルのフェッチをサポートしています。

また、オフチェーンデータの決定論的なインデックス作成、および任意のHTTPソースデータの導入の可能性についても基礎ができました。

ハンドラーの実行中に「インライン」でファイルを取得するのではなく、与えられたファイル識別子に対して新しいデータソースとして生成されるテンプレートを導入しています。これらの新しいデータソースはファイルを取得し、失敗した場合は再試行し、ファイルが見つかった場合は専用のハンドラーを実行します。

これは、既存のデータソーステンプレートと同様で、新しいチェーンベースのデータソースを動的に作成するために使用します。

既存のipfs.cat APIを置き換えるものです。

アップグレードガイド

このセクションへのリンク

graph-ts および graph-cli を更新しました。

このセクションへのリンク

ファイルデータソースは、graph-ts >=0.29.0 および graph-cli >=0.33.1 が必要です。

ファイルが見つかったときに更新される新しいエンティティタイプを追加します。

このセクションへのリンク

ファファイルが見つかったときに更新される新しいエンティティタイプを追加します。イルデータソースは、チェーンベースのエンティティにアクセスしたり更新することはできませんが、ファイル固有のエンティティを更新する必要があります。

これは、既存のエンティティからフィールドを分離し、別のエンティティにリンクさせることを意味します。

Original combined entity:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
externalURL: String!
ipfsURI: String!
image: String!
name: String!
description: String!
type: String!
updatedAtTimestamp: BigInt
owner: User!
}

New, split entity:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
ipfsURI: TokenMetadata
updatedAtTimestamp: BigInt
owner: String!
}
type TokenMetadata @entity {
id: ID!
image: String!
externalURL: String!
name: String!
description: String!
}

親エンティティと結果のファイルデータソースエンティティの間の関係が1:1である場合、最も単純なパターンは、IPFS CIDをルックアップとして使用して、親エンティティを結果のファイルエンティティにリンクすることです。新しいファイルベースのエンティティのモデリングに問題がある場合は、Discordに連絡してください。

入れ子フィルターを使用すると、これらの入れ子エンティティに基づいて、親エンティティをフィルタリングすることができます。

種類: ファイル/ipfs または 種類: ファイル/arweave の新しいテンプレートデータソースを追加します>

このセクションへのリンク

目的のファイルが特定されたときに生成されるデータソースです。

templates:
- name: TokenMetadata
kind: file/ipfs
mapping:
apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/mapping.ts
handler: handleMetadata
entities:
- TokenMetadata
abis:
- name: Token
file: ./abis/Token.json

現在、abisが必要ですが、ファイル・データ・ソース内からコントラクトを呼び出すことはできません。

ファイルデータソースは、entitiesの下で相互作用するすべてのエンティティタイプについて具体的に言及しなければなりません。詳しくはlimitationsを参照してください。

ファイルを処理するハンドラーを新規に作成

このセクションへのリンク

このハンドラは、1つのBytesパラメータを受け入れなければなりませんが、これは、ファイルが見つかったときに、その内容を処理することができます。これは多くの場合JSONファイルであり、graph-tsヘルパー(ドキュメント)で処理することができます。

読みやすい文字列としてのファイルのCIDは、dataSourceを介して次のようにアクセスできます:

const cid = dataSource.stringParam()

ハンドラーの例:

import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'
import { TokenMetadata } from '../generated/schema'
export function handleMetadata(content: Bytes): void {
let tokenMetadata = new TokenMetadata(dataSource.stringParam())
const value = json.fromBytes(content).toObject()
if (value) {
const image = value.get('image')
const name = value.get('name')
const description = value.get('description')
const externalURL = value.get('external_url')
if (name && image && description && externalURL) {
tokenMetadata.name = name.toString()
tokenMetadata.image = image.toString()
tokenMetadata.externalURL = externalURL.toString()
tokenMetadata.description = description.toString()
}
tokenMetadata.save()
}
}

必要なときにファイルデータソースを起動する

このセクションへのリンク

チェーンベースハンドラーの実行中に、ファイルデータソースを作成できるようになりました:

  • 自動生成されたtemplatesからテンプレートをインポートする。
  • マッピング内から TemplateName.create(cid: string) を呼び出します。この場合、cid は IPFS または Arweave の有効なコンテンツ識別子です

IPFS の場合、グラフノードは v0 および v1 コンテンツ識別子、およびディレクトリを持つコンテンツ識別子 (例: bafyreighykzv2we26wfrbzkcdw37sbrby4upq7ae3aqobbq7i4er3tnxci/metadata.json) をサポートします。

For Arweave, as of version 0.33.0 Graph Node can fetch files stored on Arweave based on their transaction ID from an Arweave gateway (example file). Arweave supports transactions uploaded via Bundlr, and Graph Node can also fetch files based on Bundlr manifests.

例:

import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'
const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'
//This example code is for a Crypto coven subgraph. The above ipfs hash is a directory with token metadata for all crypto coven NFTs.
export function handleTransfer(event: TransferEvent): void {
let token = Token.load(event.params.tokenId.toString())
if (!token) {
token = new Token(event.params.tokenId.toString())
token.tokenID = event.params.tokenId
token.tokenURI = '/' + event.params.tokenId.toString() + '.json'
const tokenIpfsHash = ipfshash + token.tokenURI
//This creates a path to the metadata for a single Crypto coven NFT. It concats the directory with "/" + filename + ".json"
token.ipfsURI = tokenIpfsHash
TokenMetadataTemplate.create(tokenIpfsHash)
}
token.updatedAtTimestamp = event.block.timestamp
token.owner = event.params.to.toHexString()
token.save()
}

これにより、新しいファイル データ ソースが作成され、グラフ ノードの構成済み IPFS または Arweave エンドポイントがポーリングされ、見つからない場合は再試行されます。ファイルが見つかると、ファイルデータソースハンドラが実行されます。

この例では、親 Token エンティティと結果の TokenMetadata エンティティの間のルックアップとして CID を使用しています。

以前は、サブグラフ開発者がipfs.cat(CID)を呼び出してファイルを取得するポイントでした。

おめでとうございます!ファイルデータソースが使用できます。

サブグラフのデプロイ

このセクションへのリンク

任意のグラフノードにbuildおよびdeployできるようになりました >=v0.30.0-rc.0.

ファイルデータソースハンドラおよびエンティティは、他のサブグラフエンティティから分離され、実行時に決定論的であることを保証し、チェーンベースのデータソースを汚染しないことを保証します。具体的には、以下の通りです。

  • ファイルデータソースで作成されたエンティティは不変であり、更新することはできません。
  • ファイルデータソースハンドラは、他のファイルデータソースのエンティティにアクセスすることはできません。
  • ファイルデータソースに関連するエンティティは、チェーンベースハンドラーからアクセスできません。

この制約は、ほとんどのユースケースで問題になることはありませんが、一部のユースケースでは複雑さをもたらすかもしれません。ファイルベースのデータをサブグラフでモデル化する際に問題がある場合は、Discordを通じてご連絡ください。

また、オンチェーンデータソースや他のファイルデータソースからデータソースを作成することはできません。この制限は、将来的に解除される可能性があります。

ベストプラクティス

このセクションへのリンク

NFT メタデータを対応するトークンにリンクする場合、メタデータの IPFS ハッシュを使用して、トークン エンティティから Metadata エンティティを参照します。IPFSハッシュをIDとして使用してMetadataエンティティを保存します。

ファイル データ ソースの作成時に DataSource context を使用すると、ファイル データ ソース ハンドラーで利用できるようになる追加情報を渡すことができます。

複数回リフレッシュされるエンティティがある場合は、IPFSハッシュ&スタンプとエンティティIDを使用して一意のファイルベースのエンティティを作成し、チェーンベースのエンティティ内の派生フィールドを使用してそれらを参照します。

クエリが「最新版」のみを返すように、上記の推奨事項を改善するよう取り組んでいます。

ファイル データ ソースは現在、ABI が使用されていないにもかかわらず、ABI を必要とします (issue)。回避策は、任意のABIを追加することです。

ファイルデータソースのハンドラーは、eth_callコントラクトバインディングをインポートするファイルには存在できず、「unknown import」で失敗しました。ethereum::ethereum.call has not been defined" (issue) で失敗します。回避策としては、ファイルデータソースハンドラーを専用ファイルに作成することです。

クリプトコヴェン・サブグラフの移動

GIPファイルデータソース

ページを編集

サポートされているネットワーク
AssemblyScript API
ページを編集