現像 > サブグラフの作成

サブグラフの作成

Reading time: 68 min

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

サブグラフの定義

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

In order to use your subgraph on The Graph's decentralized network, you will need to create an API key. It is recommended that you add signal to your subgraph with at least 3,000 GRT.

Before you go into detail about the contents of the manifest file, you need to install the Graph CLI which you will need to build and deploy a subgraph.

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

Once installed, the graph init command can be used to set up a new subgraph project, either from an existing contract or from an example subgraph. This command can be used to create a subgraph in Subgraph Studio by passing in graph init --product subgraph-studio. If you already have a smart contract deployed to your preferred network, bootstrapping a new subgraph from that contract can be a good way to get started.

既存のコントラクトから

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

次のコマンドは、既存のコントラクトのすべてのイベントにインデックスを付けるサブグラフを作成します。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>

The example subgraph is based on the Gravity contract by Dani Grant that manages user avatars and emits NewGravatar or UpdateGravatar events whenever avatars are created or updated. The subgraph handles these events by writing Gravatar entities to the Graph Node store and ensuring these are updated according to the events. The following sections will go over the files that make up the subgraph manifest for this example.

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

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

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
indexerHints:
prune: auto
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

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

  • specVersion: a semver version that identifies the supported manifest structure and functionality for the subgraph. The latest version is 1.2.0. See specVersion releases section to see more details on features & releases.

  • description: a human-readable description of what the subgraph is. This description is displayed in Graph Explorer when the subgraph is deployed to Subgraph Studio.

  • repository: the URL of the repository where the subgraph manifest can be found. This is also displayed in Graph Explorer.

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

  • indexerHints.prune: Defines the retention of historical block data for a subgraph. See prune in indexerHints section.

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

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

  • dataSources.source.endBlock:データ ソースがインデックス作成を停止するブロックのオプション番号 (そのブロックを含む)。必要な最小仕様バージョン: 0.0.9

  • dataSources.context:サブグラフマッピング内で使用できるキーと値のペア。BoolStringIntInt8BigDecimalBytesListBigIntのような様々なデータ型をサポートしています。各変数はtypedataを指定する必要がある。これらのコンテキスト変数は、マッピング・ファイルからアクセスすることができ、サブグラフ開発のためのより多くの設定可能なオプションを提供します。

  • 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配列に、索引化するデータが必要な各コントラクトのエントリを追加してください

Order of Triggering Handlers

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

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

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

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

Note: When new dynamic data source are created, the handlers defined for dynamic data sources will only start processing after all existing data source handlers are processed, and will repeat in the same sequence whenever triggered.

Indexed Argument Filters / Topic Filters

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

Requires: SpecVersion >= 1.2.0

Topic filters, also known as indexed argument filters, are a powerful feature in subgraphs that allow users to precisely filter blockchain events based on the values of their indexed arguments.

  • These filters help isolate specific events of interest from the vast stream of events on the blockchain, allowing subgraphs to operate more efficiently by focusing only on relevant data.

  • This is useful for creating personal subgraphs that track specific addresses and their interactions with various smart contracts on the blockchain.

How Topic Filters Work

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

When a smart contract emits an event, any arguments that are marked as indexed can be used as filters in a subgraph's manifest. This allows the subgraph to listen selectively for events that match these indexed arguments.

  • The event's first indexed argument corresponds to topic1, the second to topic2, and so on, up to topic3, since the Ethereum Virtual Machine (EVM) allows up to three indexed arguments per event.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Token {
// Event declaration with indexed parameters for addresses
event Transfer(address indexed from, address indexed to, uint256 value);
// Function to simulate transferring tokens
function transfer(address to, uint256 value) public {
// Emitting the Transfer event with from, to, and value
emit Transfer(msg.sender, to, value);
}
}

In this example:

  • The Transfer event is used to log transactions of tokens between addresses.
  • The from and to parameters are indexed, allowing event listeners to filter and monitor transfers involving specific addresses.
  • The transfer function is a simple representation of a token transfer action, emitting the Transfer event whenever it is called.

Configuration in Subgraphs

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

Topic filters are defined directly within the event handler configuration in the subgraph manifest. Here is how they are configured:

eventHandlers:
- event: SomeEvent(indexed uint256, indexed address, indexed uint256)
handler: handleSomeEvent
topic1: ['0xValue1', '0xValue2']
topic2: ['0xAddress1', '0xAddress2']
topic3: ['0xValue3']

In this setup:

  • topic1 corresponds to the first indexed argument of the event, topic2 to the second, and topic3 to the third.
  • Each topic can have one or more values, and an event is only processed if it matches one of the values in each specified topic.
  • Within a Single Topic: The logic functions as an OR condition. The event will be processed if it matches any one of the listed values in a given topic.
  • Between Different Topics: The logic functions as an AND condition. An event must satisfy all specified conditions across different topics to trigger the associated handler.

Example 1: Tracking Direct Transfers from Address A to Address B

このセクションへのリンク
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleDirectedTransfer
topic1: ['0xAddressA'] # Sender Address
topic2: ['0xAddressB'] # Receiver Address

In this configuration:

  • topic1 is configured to filter Transfer events where 0xAddressA is the sender.
  • topic2 is configured to filter Transfer events where 0xAddressB is the receiver.
  • The subgraph will only index transactions that occur directly from 0xAddressA to 0xAddressB.

Example 2: Tracking Transactions in Either Direction Between Two or More Addresses

このセクションへのリンク
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransferToOrFrom
topic1: ['0xAddressA', '0xAddressB', '0xAddressC'] # Sender Address
topic2: ['0xAddressB', '0xAddressC'] # Receiver Address

In this configuration:

  • topic1 is configured to filter Transfer events where 0xAddressA, 0xAddressB, 0xAddressC is the sender.
  • topic2 is configured to filter Transfer events where 0xAddressB and 0xAddressC is the receiver.
  • The subgraph will index transactions that occur in either direction between multiple addresses allowing for comprehensive monitoring of interactions involving all addresses.

Requires: SpecVersion >= 1.2.0. Currently, eth_calls can only be declared for event handlers.

Declarative eth_calls are a valuable subgraph feature that allows eth_calls to be executed ahead of time, enabling graph-node to execute them in parallel.

This feature does the following:

  • Significantly improves the performance of fetching data from the Ethereum blockchain by reducing the total time for multiple calls and optimizing the subgraph's overall efficiency.
  • Allows faster data fetching, resulting in quicker query responses and a better user experience.
  • Reduces wait times for applications that need to aggregate data from multiple Ethereum calls, making the data retrieval process more efficient.
  • Declarative eth_calls: Ethereum calls that are defined to be executed in parallel rather than sequentially.
  • Parallel Execution: Instead of waiting for one call to finish before starting the next, multiple calls can be initiated simultaneously.
  • Time Efficiency: The total time taken for all the calls changes from the sum of the individual call times (sequential) to the time taken by the longest call (parallel).

Scenario without Declarative eth_calls

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

Imagine you have a subgraph that needs to make three Ethereum calls to fetch data about a user's transactions, balance, and token holdings.

Traditionally, these calls might be made sequentially:

  1. Call 1 (Transactions): Takes 3 seconds
  2. Call 2 (Balance): Takes 2 seconds
  3. Call 3 (Token Holdings): Takes 4 seconds

Total time taken = 3 + 2 + 4 = 9 seconds

Scenario with Declarative eth_calls

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

With this feature, you can declare these calls to be executed in parallel:

  1. Call 1 (Transactions): Takes 3 seconds
  2. Call 2 (Balance): Takes 2 seconds
  3. Call 3 (Token Holdings): Takes 4 seconds

Since these calls are executed in parallel, the total time taken is equal to the time taken by the longest call.

Total time taken = max (3, 2, 4) = 4 seconds

  1. Declarative Definition: In the subgraph manifest, you declare the Ethereum calls in a way that indicates they can be executed in parallel.
  2. Parallel Execution Engine: The Graph Node's execution engine recognizes these declarations and runs the calls simultaneously.
  3. Result Aggregation: Once all calls are complete, the results are aggregated and used by the subgraph for further processing.

Example Configuration in Subgraph Manifest

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

Declared eth_calls can access the event.address of the underlying event as well as all the event.params.

Subgraph.yaml using event.address:

eventHandlers:
event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)
handler: handleSwap
calls:
global0X128: Pool[event.address].feeGrowthGlobal0X128()
global1X128: Pool[event.address].feeGrowthGlobal1X128()

Details for the example above:

  • global0X128 is the declared eth_call.
  • The text before colon(global0X128) is the label for this eth_call which is used when logging errors.
  • The text (Pool[event.address].feeGrowthGlobal0X128()) is the actual eth_call that will be executed, which is in the form of Contract[address].function(arguments)
  • The address and arguments can be replaced with variables that will be available when the handler is executed.

Subgraph.yaml using event.params

calls:
- ERC20DecimalsToken0: ERC20[event.params.token0].decimals()
バージョンリリースノート
1.2.0Added support for Indexed Argument Filtering & declared eth_call
1.1.0Supports Timeseries & Aggregations. Added support for type Int8 for id.
1.0.0Supports indexerHints feature to prune subgraphs
0.0.9Supports endBlock feature
0.0.8Added support for polling Block Handlers and Initialisation Handlers.
0.0.7Added support for File Data Sources.
0.0.6Supports fast Proof of Indexing calculation variant.
0.0.5Added support for event handlers having access to transaction receipts.
0.0.4Added support for managing subgraph features.

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。
IntThe GraphQL spec defines Int to be a signed 32-bit integer.
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 桁にまとめられます。
TimestampIt is an i64 value in microseconds. Commonly used for timestamp fields for timeseries and aggregations.

スキーマ内に 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
}
}
}
}

このように多対多の関係をより精巧に保存する方法では、サブグラフに保存されるデータが少なくなるため、サブグラフのインデックス作成や問い合わせが劇的に速くなります。

スキーマへのコメントの追加

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

As per GraphQL spec, comments can be added above schema entity attributes using the hash symble #. This is illustrated in the example below:

type MyFirstEntity @entity {
# unique identifier and primary key of the entity
id: Bytes!
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

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

It is highly recommended to use Bytes as the type for id fields, and only use String for attributes that truly contain human-readable text, like the name of a token. Below are some recommended id values to consider when creating new entities.

  • transfer.id = event.transaction.hash

  • let id = event.transaction.hash.concatI32(event.logIndex.toI32())

  • For entities that store aggregated data, for e.g, daily trade volumes, the id usually contains the day number. Here, using a Bytes as the id is beneficial. Determining the id would look like

let dayID = event.block.timestamp.toI32() / 86400
let id = Bytes.fromI32(dayID)
  • Convert constant addresses to Bytes.

const id = Bytes.fromHexString('0xdead...beef')

There is a Graph Typescript Library which contains utilities for interacting with the Graph Node store and conveniences for handling smart contract data and entities. It can be imported into mapping.ts from @graphprotocol/graph-ts.

Handling of entities with identical IDs

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

When creating and saving a new entity, if an entity with the same ID already exists, the properties of the new entity are always preferred during the merge process. This means that the existing entity will be updated with the values from the new entity.

If a null value is intentionally set for a field in the new entity with the same ID, the existing entity will be updated with the null value.

If no value is set for a field in the new entity with the same ID, the field will result in null as well.

スマートコントラクト、イベント、エンティティを簡単かつタイプセーフに扱うために、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 を変更するたびに、コード生成を再実行する必要があります。また、サブグラフをビルドまたはディプロイする前に、少なくとも一度は実行する必要があります。

Code generation does not check your mapping code in src/mapping.ts. If you want to check that before trying to deploy your subgraph to Graph Explorer, you can run yarn build and fix any syntax errors that the TypeScript compiler might find.

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

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

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. トランザクションの詳細ページを読み込んで、そのコントラクトの開始ブロックを見つけます。

The indexerHints setting in a subgraph's manifest provides directives for indexers on processing and managing a subgraph. It influences operational decisions across data handling, indexing strategies, and optimizations. Presently, it features the prune option for managing historical data retention or pruning.

This feature is available from specVersion: 1.0.0

indexerHints.prune: Defines the retention of historical block data for a subgraph. Options include:

  1. "never": No pruning of historical data; retains the entire history.
  2. "auto": Retains the minimum necessary history as set by the indexer, optimizing query performance.
  3. A specific number: Sets a custom limit on the number of historical blocks to retain.
indexerHints:
prune: auto

The term "history" in this context of subgraphs is about storing data that reflects the old states of mutable entities.

History as of a given block is required for:

  • Time travel queries, which enable querying the past states of these entities at specific blocks throughout the subgraph's history
  • Using the subgraph as a graft base in another subgraph, at that block
  • Rewinding the subgraph back to that block

If historical data as of the block has been pruned, the above capabilities will not be available.

Using "auto" is generally recommended as it maximizes query performance and is sufficient for most users who do not require access to extensive historical data.

For subgraphs leveraging time travel queries, it's advisable to either set a specific number of blocks for historical data retention or use prune: never to keep all historical entity states. Below are examples of how to configure both options in your subgraph's settings:

To retain a specific amount of historical data:

indexerHints:
prune: 1000 # Replace 1000 with the desired number of blocks to retain

To preserve the complete history of entity states:

indexerHints:
prune: never

You can check the earliest block (with historical state) for a given subgraph by querying the Indexing Status API:

{
indexingStatuses(subgraphs: ["Qm..."]) {
subgraph
synced
health
chains {
earliestBlock {
number
}
latestBlock {
number
}
chainHeadBlock { number }
}
}
}

Note that the earliestBlock is the earliest block with historical data, which will be more recent than the startBlock specified in the manifest, if the subgraph has been pruned.

Event handlers in a subgraph react to specific events emitted by smart contracts on the blockchain and trigger handlers defined in the subgraph's manifest. This enables subgraphs to process and store event data according to defined logic.

Defining an Event Handler

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

An event handler is declared within a data source in the subgraph's YAML configuration. It specifies which events to listen for and the corresponding function to execute when those events are detected.

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
eventHandlers:
- event: Approval(address,address,uint256)
handler: handleApproval
- event: Transfer(address,address,uint256)
handler: handleTransfer
topic1: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', '0xc8dA6BF26964aF9D7eEd9e03E53415D37aA96325'] # Optional topic filter which filters only events with the specified topic.

コールハンドラー

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

イベントはコントラクトの状態に対する関連する変更を収集するための効果的な方法を提供しますが、多くのコントラクトはガスコストを最適化するためにログの生成を避けます。このような場合、サブグラフはデータソース・コントラクトに行われたコールを購読することができます。これは、関数シグネチャを参照するコールハンドラと、この関数へのコールを処理するマッピングハンドラを定義することで実現します。これらのコールを処理するために、マッピングハンドラは、コールへの入力とコールからの出力を型付けした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

ポーリングフィルター

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

specVersion >= 0.0.8 が必要です。

注: ポーリング フィルタは、kind: ethereum の dataSource でのみ使用できます。

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

定義されたハンドラーは、n ブロックごとに 1 回呼び出されます。n は、every フィールドで指定された値です。 この構成により、サブグラフが定期的なブロック間隔で特定の操作を実行できるようになります。

ワンスフィルター

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

specVersion >= 0.0.8 が必要です。

注: Once フィルタは、kind: ethereum の dataSource でのみ使用できます。

blockHandlers:
- handler: handleOnce
filter:
kind: once

Once フィルターを使用して定義されたハンドラーは、他のすべてのハンドラーが実行される前に 1 回だけ呼び出されます。 この構成により、サブグラフはハンドラーを初期化ハンドラーとして使用し、インデックス作成の開始時に特定のタスクを実行できるようになります。

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

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

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

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

Timeseries and Aggregations

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

Timeseries and aggregations enable your subgraph to track statistics like daily average price, hourly total transfers, etc.

This feature introduces two new types of subgraph entity. Timeseries entities record data points with timestamps. Aggregation entities perform pre-declared calculations on the Timeseries data points on an hourly or daily basis, then store the results for easy access via GraphQL.

type Data @entity(timeseries: true) {
id: Int8!
timestamp: Timestamp!
price: BigDecimal!
}
type Stats @aggregation(intervals: ["hour", "day"], source: "Data") {
id: Int8!
timestamp: Timestamp!
sum: BigDecimal! @aggregate(fn: "sum", arg: "price")
}

Defining Timeseries and Aggregations

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

Timeseries entities are defined with @entity(timeseries: true) in schema.graphql. Every timeseries entity must have a unique ID of the int8 type, a timestamp of the Timestamp type, and include data that will be used for calculation by aggregation entities. These Timeseries entities can be saved in regular trigger handlers, and act as the “raw data” for the Aggregation entities.

Aggregation entities are defined with @aggregation in schema.graphql. Every aggregation entity defines the source from which it will gather data (which must be a Timeseries entity), sets the intervals (e.g., hour, day), and specifies the aggregation function it will use (e.g., sum, count, min, max, first, last). Aggregation entities are automatically calculated on the basis of the specified source at the end of the required interval.

Available Aggregation Intervals

このセクションへのリンク
  • hour: sets the timeseries period every hour, on the hour.
  • day: sets the timeseries period every day, starting and ending at 00:00.

Available Aggregation Functions

このセクションへのリンク
  • sum: Total of all values.
  • count: Number of values.
  • min: Minimum value.
  • max: Maximum value.
  • first: First value in the period.
  • last: Last value in the period.

Example Aggregations Query

このセクションへのリンク
{
stats(interval: "hour", where: { timestamp_gt: 1704085200 }) {
id
timestamp
sum
}
}

Note:

To use Timeseries and Aggregations, a subgraph must have a spec version ≥1.1.0. Note that this feature might undergo significant changes that could affect backward compatibility.

Read more about Timeseries and Aggregations.

致命的でないエラー

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

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

注: グラフ ネットワークはまだ致命的ではないエラーをサポートしていないため、開発者はその機能を使用するサブグラフを 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"
}
]

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

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

注: 最初に Graph Network にアップグレードするときにグラフティングを使用することはお勧めしません。 詳細についてはこちらをご覧ください。

サブグラフが最初にデプロイされると、対応するチェーンのジェネシス ブロック (または各データ ソースで定義された 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 File Data Sources

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

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

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

Rather than fetching files "in line" during handler execution, this introduces templates which can be spawned as new data sources for a given file identifier. These new data sources fetch the files, retrying if they are unsuccessful, running a dedicated handler when the file is found.

This is similar to the existing data source templates, which are used to dynamically create new chain-based data sources.

既存の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に連絡してください。

You can use nested filters to filter parent entities on the basis of these nested entities.

種類: ファイル/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が必要ですが、ファイル・データ・ソース内からコントラクトを呼び出すことはできません。

The file data source must specifically mention all the entity types which it will interact with under entities. See limitations for more details.

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

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

This handler should accept one Bytes parameter, which will be the contents of the file, when it is found, which can then be processed. This will often be a JSON file, which can be processed with graph-ts helpers (documentation).

読みやすい文字列としてのファイルの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 Irys (previously Bundlr), and Graph Node can also fetch files based on Irys 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エンティティを保存します。

You can use DataSource context when creating File Data Sources to pass extra information which will be available to the File Data Source handler.

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

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

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

Handlers for File Data Sources cannot be in files which import eth_call contract bindings, failing with "unknown import: ethereum::ethereum.call has not been defined" (issue). Workaround is to create file data source handlers in a dedicated file.

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

GIPファイルデータソース

ページを編集

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