Goldsky 🧮によるBerachainのデータのインデックスとクエリ [Berachain翻訳]


:::note info
本記事は下記の翻訳となります。
[『Index & Query Berachain Data with Goldsky 🧮』](https://blog.berachain.com/blog/index-query-berachain-data-with-goldsky)
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1926720/b5e53715-cfce-054a-c991-77e117479f62.png)

## Goldsky を使用した Berachain データのインデックスとクエリ 🧮

### Goldsky とは何か、そしてなぜインデックスが重要なのか？

[Goldsky](https://goldsky.com/?ref=berachain.ghost.io)は、開発者がブロックチェーンからデータを保存・クエリするための[GraphQL](https://graphql.org/?ref=berachain.ghost.io) API を構築することを可能にします。このデータは高度にカスタマイズ可能で、時間の経過に伴うトークン供給の成長などのトレンドや、ユーザーのトークン残高などの瞬時のデータを明らかにするために使用できます。

subgraph には、ブロックチェーンからデータをインデックス化し、生データを変換して簡単にクエリできる形式で保存するためのロジックが含まれています。

このチュートリアルでは、[**Berachain**](https://www.berachain.com/?ref=berachain.ghost.io)ネットワーク上のユーザーの ERC20 残高をクエリする subgraph の開発方法を説明します。

### 前提条件 📋

- Nodejs `v20.11.0` 以上
- pnpm
- IDE（VSCode、Replit など）

[The Graph](https://thegraph.com/?ref=berachain.ghost.io)に既存の subgraph をデプロイしており、Berchain/Goldsky にデプロイしたい方は、**Goldsky でのセットアップ**のセクションまでスキップしてください。Goldsky と The Graph の subgraph は完全に互換性があります！

### Subgraph の構築 🛠️

まず、ターミナルで以下を入力します：

```bash
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開発ツールを提供します
```

プロジェクトのルートに、以下のプロジェクト構造を作成します（ファイルは今は空で構いません）：

```bash
# FROM: ./goldsky-subgraph;

.
├── abis
│   └── Erc20.json
├── package.json
├── schema.graphql
├── src
│   ├── mapping.ts
│   ├── utils.ts
└── subgraph.yaml
```

`./abis/Erc20.json`に以下の内容を貼り付けます。これは ERC20 コントラクトインターフェースを定義します：

```json
[
  {
    "constant": true,
    "inputs": [],
    "name": "name",
    "outputs": [{ "name": "", "type": "string" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  }
  // ... (残りのABIは省略)
]
```

#### Subgraph の設定

subgraph を作成する主なステップは、データソースの読み取り元と、このデータをインデックス化するデータ構造（エンティティ）を定義することです。これは`subgraph.yaml`、つまり subgraph マニフェストで実現されます。

`./subgraph.yaml`に以下を貼り付けます：

```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
```

このマニフェストでは、いくつかの注目すべき点があります：

1. `source`: アドレスは[bHONEY トークン](https://bartio.beratrail.io/address/0x1306D3c36eC7E38dd2c128fBe3097C2C2449af64?ref=berachain.ghost.io)で、`Erc20`インターフェースを使用し、デプロイされた`startBlock`からインデックス化されます
2. `entities`はクエリ可能な JavaScript オブジェクトと考えることができます。エンティティは互いにリレーショナルマッピングを持つことができ、GraphQL スキーマ（以下）で定義されます
3. `eventHandlers`: トークンの`Transfer`イベントが発行されるたびに、`./src/mapping.ts`の`handleTransfer`メソッドが呼び出され、インデックス化ロジックを実行します

#### スキーマの作成

`./schema.graphql`で、subgraph 内に存在する異なるエンティティのプロパティと関係を含むスキーマを定義します：

```graphql
# トークンの詳細
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`に以下のコードを追加します：

```typescript
//生成されたファイルからイベントクラスをインポート
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)`の情報を含んでいます。この情報により、ハンドラは以下の機能を実行できます：

1. トークンの詳細を取得
2. アカウントの詳細を取得
3. アカウント残高を更新

高レベルでは、このハンドラコードは`Transfer`イベントが発行されるたびに ERC20 アカウント残高を忠実に更新します。

#### **エンティティの操作**

`mapping.ts`のコードがかなりシンプルであることに気付いたかもしれません - これは、エンティティとの相互作用の重要な部分がユーティリティファイルに抽象化されているためです。では、subgraph エンティティの操作の詳細を見ていきましょう。

`./src/utils.ts`に以下のコードを追加します：

```typescript
//生成されたファイルからスマートコントラクトクラスをインポート
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 のビルド

プロジェクトのルートディレクトリで、ターミナルで以下を実行します：

```bash
# FROM: ./goldsky-subgraph;

pnpm codegen;
pnpm build;
```

これらのコマンドは、コントラクト ABI から TypeScript クラスファイルを生成し、コードをコンパイルし、`/build`ディレクトリにビルド出力を作成します。

デプロイする前に、Goldsky でセットアップを行う必要があります。

### Goldsky でのセットアップ

Goldsky はあなたの subgraph をホストし、必要なすべてのインデックス作成を行います。以下の手順に従ってアカウントをセットアップしてください：

1. [app.goldsky.com](https://app.goldsky.com/?ref=berachain.ghost.io)でアカウントを作成
2. [設定ページ](https://app.goldsky.com/dashboard/settings?ref=berachain.ghost.io)で API キーを作成
3. Goldsky CLI をインストール：

```bash
curl https://goldsky.com | sh
```

4. 先ほど作成した API キーでログイン：

```bash
goldsky login
```

#### Subgraph のデプロイ 🚀

プロジェクトのルートで、以下を実行します：

```bash
# FROM: ./goldsky-subgraph;

goldsky subgraph deploy erc20-subgraph/1.0.0 --path .
```

正常にデプロイされたら、[デプロイされた subgraph を表示](https://app.goldsky.com/dashboard/subgraphs?ref=berachain.ghost.io)できます。インデックス作成プロセスではトークン残高を更新するためにすべてのブロックを確認する必要があるため、すぐには使用できません。

![](https://berachain.ghost.io/content/images/2024/05/1-yvrvozauxz1v7brochyczw.png)

Goldsky Subgraph ダッシュボード

#### データのクエリ

ダッシュボードには、クエリを書くための良いインターフェースを提供する「Public GraphQL link」が表示されます。では、この新しい subgraph を以下のクエリで試してみましょう：

```graphql
{
  accounts {
    id
    balances {
      id
      token {
        id
        name
        symbol
        decimals
      }
      amount
    }
  }
}
```

一緒に試したい例がある場合は、この[ライブ subgraph](https://api.goldsky.com/api/public/project_clteviu2hajla01r51uil7cp5/subgraphs/erc20-subgraph/1.0.0/gn?ref=berachain.ghost.io)を使用してください。

ユーザーの MIM 残高を Berachain の[ブロックエクスプローラー](https://artio.beratrail.io/address/0x000000000746a93872fecf15c2f22b998131df94?ref=berachain.ghost.io)と照合すると、subgraph がユーザーのトークン残高を正確にインデックス化していることがわかります ✅

![](https://berachain.ghost.io/content/images/2024/05/1-o1abuohqkfva2zlalpfziq.png)

Subgraph とブロックエクスプローラーのトークン残高の比較

#### まとめ

これで、Goldsky の subgraphs を使用して Berachain ウォレットのトークン残高をインデックス化する方法を説明しました。Goldsky はデータ可用性プラットフォームで、開発者がカスタマイズされたブロックチェーンデータを簡単に保存・クエリする方法を提供します。

---

#### 🐻 完全なコードリポジトリ

最終的なコードを確認し、他のガイドを見たい場合は、[**_Berachain Goldsky Guide Code_**](https://github.com/berachain/guides/tree/main/apps/goldsky-subgraph?ref=berachain.ghost.io)をチェックしてください。

#### 🛠️ さらに構築したい場合は？

Berachain でさらに構築し、より多くの例を見たい場合は、[**_Berachain GitHub Guides リポジトリ_**](https://github.com/berachain/guides?ref=berachain.ghost.io)をご覧ください。NextJS、Hardhat、Viem、Foundry などを含む、幅広い実装例があります。

より詳細な情報を探索したい場合は、[**_Berachain Docs_**](https://docs.berachain.com/?ref=berachain.ghost.io)をご覧ください。

#### 開発サポートをお探しですか？

[**_Berachain Discord_**](https://discord.com/invite/berachain?ref=berachain.ghost.io)サーバーに参加し、開発者チャンネルで質問をしてください。

❤️ この記事への愛を示すことを忘れずに 👏🏼

---

---

:::note info
【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction（手数料抽象化）を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。

- [Sunrise - Specialized DA Layer for Proof of Liquidity](https://sunriselayer.io/)
- [Sunrise / Gluon Docs](https://docs.sunriselayer.io/)

【Social Links】

- [X(旧 Twitter)[英語])](https://twitter.com/SunriseLayer)
- [X(旧 Twitter)[日本語])](https://twitter.com/SunriseLayer)
- [Discord Community](https://discord.com/invite/sunrise)
- [Medium](https://sunriselayer.medium.com/)
- [Github](https://github.com/sunriselayer)
- [Linkedin](https://www.linkedin.com/company/sunriselayer)

【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 [Google Form](https://forms.gle/h8RVahxRtXUvYwnv6)

![1080x360.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839047/8971d83b-3331-4757-dc72-320d28618735.jpeg)

:::