本記事は下記の翻訳となります。
『Index & Query Berachain Data with Goldsky 🧮』
Goldsky を使用した Berachain データのインデックスとクエリ 🧮
Goldsky とは何か、そしてなぜインデックスが重要なのか?
Goldskyは、開発者がブロックチェーンからデータを保存・クエリするためのGraphQL API を構築することを可能にします。このデータは高度にカスタマイズ可能で、時間の経過に伴うトークン供給の成長などのトレンドや、ユーザーのトークン残高などの瞬時のデータを明らかにするために使用できます。
subgraph には、ブロックチェーンからデータをインデックス化し、生データを変換して簡単にクエリできる形式で保存するためのロジックが含まれています。
このチュートリアルでは、Berachainネットワーク上のユーザーの ERC20 残高をクエリする subgraph の開発方法を説明します。
前提条件 📋
- Nodejs
v20.11.0
以上 - pnpm
- IDE(VSCode、Replit など)
The Graphに既存の subgraph をデプロイしており、Berchain/Goldsky にデプロイしたい方は、Goldsky でのセットアップのセクションまでスキップしてください。Goldsky と The Graph の subgraph は完全に互換性があります!
Subgraph の構築 🛠️
まず、ターミナルで以下を入力します:
mkdir goldsky-subgraph;
cd goldsky-subgraph;
pnpm init;
pnpm install @graphprotocol/graph-cli @graphprotocol/graph-ts;
# すべてのデフォルトを受け入れ、プロンプトが表示されたらEnterを押します
# name: (project-name) project-name
# version: (0.0.0) 0.0.1
# description: The Project Description
# entry point: //空のまま
# test command: //空のまま
# git repository: //リポジトリのURL
# keywords: //空のまま
# author: //あなたの名前
# license: N/A
# @graphprotocolの依存関係はsubgraph開発ツールを提供します
プロジェクトのルートに、以下のプロジェクト構造を作成します(ファイルは今は空で構いません):
# FROM: ./goldsky-subgraph;
.
├── abis
│ └── Erc20.json
├── package.json
├── schema.graphql
├── src
│ ├── mapping.ts
│ ├── utils.ts
└── subgraph.yaml
./abis/Erc20.json
に以下の内容を貼り付けます。これは ERC20 コントラクトインターフェースを定義します:
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{ "name": "", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
// ... (残りのABIは省略)
]
Subgraph の設定
subgraph を作成する主なステップは、データソースの読み取り元と、このデータをインデックス化するデータ構造(エンティティ)を定義することです。これはsubgraph.yaml
、つまり subgraph マニフェストで実現されます。
./subgraph.yaml
に以下を貼り付けます:
specVersion: 0.0.4
description: ERC-20 subgraph with event handlers & entities
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Erc20
network: berachain-bartio
source:
address: '0x1306D3c36eC7E38dd2c128fBe3097C2C2449af64'
abi: Erc20
startBlock: 88948
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Token
- Account
- TokenBalance
abis:
- name: Erc20
file: ./abis/Erc20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
このマニフェストでは、いくつかの注目すべき点があります:
-
source
: アドレスはbHONEY トークンで、Erc20
インターフェースを使用し、デプロイされたstartBlock
からインデックス化されます -
entities
はクエリ可能な JavaScript オブジェクトと考えることができます。エンティティは互いにリレーショナルマッピングを持つことができ、GraphQL スキーマ(以下)で定義されます -
eventHandlers
: トークンのTransfer
イベントが発行されるたびに、./src/mapping.ts
のhandleTransfer
メソッドが呼び出され、インデックス化ロジックを実行します
スキーマの作成
./schema.graphql
で、subgraph 内に存在する異なるエンティティのプロパティと関係を含むスキーマを定義します:
# トークンの詳細
type Token @entity {
id: ID!
#トークン名
name: String!
#トークンシンボル
symbol: String!
#使用される小数点
decimals: BigDecimal!
}
# アカウントの詳細
type Account @entity {
#アカウントアドレス
id: ID!
#残高
balances: [TokenBalance!]! @derivedFrom(field: "account")
}
# トークン残高の詳細
type TokenBalance @entity {
id: ID!
#トークン
token: Token!
#アカウント
account: Account!
#金額
amount: BigDecimal!
}
Token
エンティティはシンプルで、ERC20 トークンの既知のプロパティを含んでいます。
Account
エンティティには、ウォレットアドレスのid
と、より興味深いことに、TokenBalance
型のbalances
のリストが含まれています。@derivedFrom
ディレクティブは、アカウントのbalances
プロパティがTokenBalance
エンティティのaccount
プロパティに基づく逆引きによって定義されることを意味します。
TokenBalance
はAccount
とToken
の両方のエンティティを活用して、各特定のユーザーのトークン残高を定義します。
このチュートリアルでは、Token
とTokenBalance
の両方のエンティティを持つことは絶対に必要というわけではありませんでした。ユーザーの MIM 残高は、Account
エンティティだけで捕捉することも考えられました。しかし、この設計により、複数のトークン残高を捕捉するように subgraph を拡張することが可能になります。
マッピングの作成
マッピングファイルは、すべてが一つになる場所です ✨ ブロックチェーンデータは、スキーマで定義したエンティティと関連付けられます。以下では、handleTransfer
イベントハンドラが呼び出されたときに発生する相互作用を定義します。
./src/mapping.ts
に以下のコードを追加します:
//生成されたファイルからイベントクラスをインポート
import { Transfer } from '../generated/Erc20/Erc20';
//utils.tsで定義された関数をインポート
import { fetchTokenDetails, fetchAccount, updateTokenBalance } from './utils';
//データ型をインポート
import { BigInt } from '@graphprotocol/graph-ts';
export function handleTransfer(event: Transfer): void {
// 1. トークンの詳細を取得
let token = fetchTokenDetails(event);
if (!token) {
return;
}
// 2. アカウントの詳細を取得
let fromAddress = event.params.from.toHex();
let toAddress = event.params.to.toHex();
let fromAccount = fetchAccount(fromAddress);
let toAccount = fetchAccount(toAddress);
if (!fromAccount || !toAccount) {
return;
}
// 3. トークン残高を更新
// 'from'アカウントのトークン残高を設定
updateTokenBalance(token, fromAccount, BigInt.fromI32(0).minus(event.params.value));
// 'to'アカウントのトークン残高を設定
updateTokenBalance(token, toAccount, event.params.value);
}
handleTransfer
はTransfer
イベントをパラメータとして取り、(fromAddress, toAddress, transferAmount)
の情報を含んでいます。この情報により、ハンドラは以下の機能を実行できます:
- トークンの詳細を取得
- アカウントの詳細を取得
- アカウント残高を更新
高レベルでは、このハンドラコードはTransfer
イベントが発行されるたびに ERC20 アカウント残高を忠実に更新します。
エンティティの操作
mapping.ts
のコードがかなりシンプルであることに気付いたかもしれません - これは、エンティティとの相互作用の重要な部分がユーティリティファイルに抽象化されているためです。では、subgraph エンティティの操作の詳細を見ていきましょう。
./src/utils.ts
に以下のコードを追加します:
//生成されたファイルからスマートコントラクトクラスをインポート
import { Erc20 } from '../generated/Erc20/Erc20';
//エンティティをインポート
import { Account, Token, TokenBalance } from '../generated/schema';
//データ型をインポート
import { BigDecimal, ethereum, BigInt } from '@graphprotocol/graph-ts';
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
// トークンの詳細を取得
export function fetchTokenDetails(event: ethereum.Event): Token | null {
//トークンの詳細が既に保存されているかチェック
let token = Token.load(event.address.toHex());
if (!token) {
//トークンの詳細が利用できない場合
//新しいトークンを作成
token = new Token(event.address.toHex());
//デフォルト値を設定
token.name = 'N/A';
token.symbol = 'N/A';
token.decimals = BigDecimal.fromString('0');
//コントラクトをバインド
let erc20 = Erc20.bind(event.address);
//名前を取得
let tokenName = erc20.try_name();
if (!tokenName.reverted) {
token.name = tokenName.value;
}
//シンボルを取得
let tokenSymbol = erc20.try_symbol();
if (!tokenSymbol.reverted) {
token.symbol = tokenSymbol.value;
}
//小数点を取得
let tokenDecimal = erc20.try_decimals();
if (!tokenDecimal.reverted) {
token.decimals = BigDecimal.fromString(tokenDecimal.value.toString());
}
//詳細を保存
token.save();
}
return token;
}
// アカウントの詳細を取得
export function fetchAccount(address: string): Account | null {
//アカウントの詳細が既に保存されているかチェック
let account = Account.load(address);
if (!account) {
//アカウントの詳細が利用できない場合
//新しいアカウントを作成
account = new Account(address);
account.save();
}
return account;
}
export function updateTokenBalance(token: Token, account: Account, amount: BigInt): void {
// ゼロアドレスは更新しない
if (ZERO_ADDRESS == account.id) return;
// 既存のアカウント残高を取得するか新しいものを作成
let accountBalance = getOrCreateAccountBalance(account, token);
let balance = accountBalance.amount.plus(bigIntToBigDecimal(amount));
// アカウント残高を更新
accountBalance.amount = balance;
accountBalance.save();
}
function getOrCreateAccountBalance(account: Account, token: Token): TokenBalance {
let id = token.id + '-' + account.id;
let tokenBalance = TokenBalance.load(id);
// 残高がまだ保存されていない場合
// 新しいTokenBalanceインスタンスを作成
if (!tokenBalance) {
tokenBalance = new TokenBalance(id);
tokenBalance.account = account.id;
tokenBalance.token = token.id;
tokenBalance.amount = BigDecimal.fromString('0');
tokenBalance.save();
}
return tokenBalance;
}
function bigIntToBigDecimal(quantity: BigInt, decimals: i32 = 18): BigDecimal {
return quantity.divDecimal(
BigInt.fromI32(10)
.pow(decimals as u8)
.toBigDecimal()
);
}
-
fetchAccount()
はAccount
エンティティを返します。まず、渡されたアドレスを持つAccount
エンティティが存在するかどうかを確認します。存在しない場合は新しいものを作成します -
fetchTokenDetails()
はToken
エンティティを返します。Token
が存在しない場合、トークンアドレスをERC20
インターフェースとバインドして新しいものをインスタンス化し、トークンコントラクトのパブリックな読み取り関数にアクセスできるようにします。これにより、名前、シンボル、小数点などのトークンプロパティを取得して設定できます -
updateTokenBalance()
はおそらく最も重要で、各転送でユーザーのTokenBalance
エンティティを更新します。mapping.ts
ファイルを参照すると、この関数は各Transfer
イベントで 2 回呼び出されます - 転送者には負のamount
が渡され、トークン残高の損失を示し、逆に、受信者には正のamount
が渡されます。このように、トークン残高の正確な会計が維持されます
Subgraph のビルド
プロジェクトのルートディレクトリで、ターミナルで以下を実行します:
# FROM: ./goldsky-subgraph;
pnpm codegen;
pnpm build;
これらのコマンドは、コントラクト ABI から TypeScript クラスファイルを生成し、コードをコンパイルし、/build
ディレクトリにビルド出力を作成します。
デプロイする前に、Goldsky でセットアップを行う必要があります。
Goldsky でのセットアップ
Goldsky はあなたの subgraph をホストし、必要なすべてのインデックス作成を行います。以下の手順に従ってアカウントをセットアップしてください:
- app.goldsky.comでアカウントを作成
- 設定ページで API キーを作成
- Goldsky CLI をインストール:
curl https://goldsky.com | sh
- 先ほど作成した API キーでログイン:
goldsky login
Subgraph のデプロイ 🚀
プロジェクトのルートで、以下を実行します:
# FROM: ./goldsky-subgraph;
goldsky subgraph deploy erc20-subgraph/1.0.0 --path .
正常にデプロイされたら、デプロイされた subgraph を表示できます。インデックス作成プロセスではトークン残高を更新するためにすべてのブロックを確認する必要があるため、すぐには使用できません。
Goldsky Subgraph ダッシュボード
データのクエリ
ダッシュボードには、クエリを書くための良いインターフェースを提供する「Public GraphQL link」が表示されます。では、この新しい subgraph を以下のクエリで試してみましょう:
{
accounts {
id
balances {
id
token {
id
name
symbol
decimals
}
amount
}
}
}
一緒に試したい例がある場合は、このライブ subgraphを使用してください。
ユーザーの MIM 残高を Berachain のブロックエクスプローラーと照合すると、subgraph がユーザーのトークン残高を正確にインデックス化していることがわかります ✅
Subgraph とブロックエクスプローラーのトークン残高の比較
まとめ
これで、Goldsky の subgraphs を使用して Berachain ウォレットのトークン残高をインデックス化する方法を説明しました。Goldsky はデータ可用性プラットフォームで、開発者がカスタマイズされたブロックチェーンデータを簡単に保存・クエリする方法を提供します。
🐻 完全なコードリポジトリ
最終的なコードを確認し、他のガイドを見たい場合は、Berachain Goldsky Guide Codeをチェックしてください。
🛠️ さらに構築したい場合は?
Berachain でさらに構築し、より多くの例を見たい場合は、Berachain GitHub Guides リポジトリをご覧ください。NextJS、Hardhat、Viem、Foundry などを含む、幅広い実装例があります。
より詳細な情報を探索したい場合は、Berachain Docsをご覧ください。
開発サポートをお探しですか?
Berachain Discordサーバーに参加し、開発者チャンネルで質問をしてください。
❤️ この記事への愛を示すことを忘れずに 👏🏼
【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction(手数料抽象化)を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。
【Social Links】
【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 Google Form