[ERC7769] ERC4337に準拠したコントラクトアカウントのためのJSON-RPC APIを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC4337**に準拠したコントラクトアカウントが、**UserOperation**をbundlerに送付するための新しいJSON-RPC APIメソッドを提案している**ERC7769**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7769

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

**ERC7769**では、新しいJSON-RPC APIメソッドを定義して**ERC4337**に準拠したコントラクトアカウントが**UserOperation**のmempoolノードやバンドラー（bundler）と通信できるようにする仕組みを提案しています。
これにより、従来のEthereumトランザクションと同じような機能が提供されます。
さらに、開発やテスト、デバッグをしやすくするために、デバッグ用のJSON-RPC APIメソッドも追加されています。

:::note info
**ERC4337**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/8c97ca7c93c4557ebfc2

# 動機

**ERC4337**では、Ethereumの通常のトランザクションの代わりに**UserOperation**オブジェクトが使われます。
**UserOperation**には、ユーザーが実行したい処理内容がすべて含まれています。

しかし、既存のEthereumのJSON-RPC APIメソッドは、この**UserOperation**オブジェクトを扱うことを想定していません。
そのため、**ERC4337**のmempoolをスムーズに運用するには、**ERC4337**対応のすべての実装が共通で利用できる標準的なAPIが必要になります。
この標準化されたAPIがあれば、異なる実装でも互換性が確保され、開発者はどの**ERC4337**環境でも同じAPIを使って操作できるようになります。

# 仕様

## bundlerとは？  
bundlerは、**ERC4337**の**UserOperation**を収集し、まとめてEthereumネットワークに送信する役割を持つノードです。
具体的には、複数の**UserOperation**をバンドルし、それを**EntryPoint**コントラクトの`handleOps`関数を使って一度に処理します。
この仕組みにより、従来のEthereumトランザクションとは異なる方法でトランザクションが管理されます。

## eth_sendUserOperationとは？

`eth_sendUserOperation`は、**UserOperation**オブジェクトを**UserOperation**mempoolに送信するためのJSON-RPCメソッドです。
このメソッドは、送信された**UserOperation**を検証し、適切なレスポンスを返します。  

正常に処理された場合、**UserOperation**のハッシュ（`userOpHash`）を返します。
ただし、バリデーションに失敗した場合や、メモリプールに追加できない場合は、エラーメッセージを返す仕様になっています。  

```js
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendUserOperation",
  "params": [
    {
      sender, // address
      nonce, // uint256
      factory, // address
      factoryData, // bytes
      callData, // bytes
      callGasLimit, // uint256
      verificationGasLimit, // uint256
      preVerificationGas, // uint256
      maxFeePerGas, // uint256
      maxPriorityFeePerGas, // uint256
      paymaster, // address
      paymasterVerificationGasLimit, // uint256
      paymasterPostOpGasLimit, // uint256
      paymasterData, // bytes
      signature // bytes
    },
    entryPoint // address
  ]
}
```

## パラメータの詳細  
このメソッドには、**UserOperation**の各要素を含むオブジェクトと、処理を実行する**EntryPoint**コントラクトのアドレスを渡します。  

`UserOperation`には、送信者のアドレス（`sender`）、実行に必要なガスの情報（`callGasLimit`、`verificationGasLimit`など）、署名（`signature`）、ペイマスター（`paymaster`）に関する情報などが含まれます。  
`entryPoint`は、この**UserOperation**を処理する**EntryPoint**コントラクトのアドレスです。  

これらのパラメータは、基本的にすべて16進数（hex）表記で指定する必要があります。
また、`factory`と`factoryData`、`paymaster`関連のパラメータは、それぞれセットで指定しなければなりません。  

## 返り値とエラーコード  

### 成功時のレスポンス  
**UserOperation**が正常にメモリプールに追加された場合、そのハッシュ（`userOpHash`）が返されます。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x123456789012345678901234567890123456789012345678901234567890abcd"
}
```
  
### 失敗時のレスポンス  

何らかの理由で**UserOperation**が受理されなかった場合、エラーメッセージとエラーコードが返されます。

- **無効なUserOperation構造（コード：-32602）**  
    - UserOperationのフォーマットやフィールドが不正な場合に返されます。
- **ウォレット作成または検証の失敗（コード：-32500）**  
    - **EntryPoint**コントラクトの`simulateValidation`で検証に失敗した場合、このエラーが返ります。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "message": "AA21 didn't pay prefund",
    "code": -32500
  }
}
```

- **ペイマスターの検証失敗（コード：-32501）**  
    - ペイマスター（**UserOperation**の手数料を支払うコントラクト）の`validatePaymasterUserOp`で拒否された場合に返されます。

- **ERC7562のルール違反（コード：-32502）**  
    - **UserOperation**が**ERC7562**のオペコードの検証ルールに違反している場合に返されます。

:::note info
**ERC7562**については以下の記事を参考にしてください。
:::


- **有効期限切れ（コード：-32503）**  
    - ウォレットまたはペイマスターによって設定された有効期限が切れている場合に返されます。
    - このエラーが返された場合、`validUntil`や`validAfter`の情報が含まれます。

- **ペイマスターのスロットリングや禁止（コード：-32504）**  
    - **ERC7562**のレピュテーションルールに基づき、ペイマスターが制限されている場合に返されます。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "message": "paymaster stake too low",
    "data": {
      "paymaster": "0x123456789012345678901234567890123456790",
      "minimumStake": "0xde0b6b3a7640000",
      "minimumUnstakeDelay": "0x15180"
    },
    "code": -32504
  }
}
```

- **ウォレットの署名検証失敗（コード：-32507）**  
    - ウォレットの署名チェックが失敗した場合に返されます。

- **ペイマスターの残高不足（コード：-32508）**  
    - ペイマスターの残高が不足している場合に返されます。
    - このエラーが発生した場合、他の**UserOperation**の影響で資金が足りなくなっている可能性があります。

## eth_estimateUserOperationGasとは？  

`eth_estimateUserOperationGas`は、**UserOperation**のガス使用量を見積もるためのJSON-RPCメソッドです。
このメソッドでは、**UserOperation**の実行に必要なガスの上限を計算します。
特に、ガス制限（gas limit）や手数料（gas price）を指定しなくても、必要な値を自動的に見積もってくれます。  

署名フィールドはウォレット側で無視されるため、ガス見積もり時にユーザーの承認を必要としません。
ただし、適切なデータ構造を維持するため、署名フィールドには適当な長さのダミー値（例えば、適切な長さの空の署名データ）を入れる必要があります。  

このメソッドでは、オプションとして「State Override Set」を受け取ることができ、これを使うとガス見積もり時に一時的にブロックチェーンの状態を変更できます。
これは、`eth_call`メソッドのオーバーライド機能と同じ仕組みです。  

返り値として、以下のガス値が返されます。  
- `preVerificationGas`
    - **UserOperation**の実行前のオーバーヘッドガス量。 
- `verificationGasLimit`
    - **UserOperation**の検証に必要なガス量。
- `paymasterVerificationGasLimit`
    - ペイマスターの検証に必要なガス量（ペイマスターが指定されている場合のみ）。
- `callGasLimit`
    - アカウント内での処理に必要なガス量。

`postOpGasLimit`は正確に見積もることができないため、ペイマスターがこの値を指定してチェーン上で適切に処理する必要があります。  

エラーが発生した場合は、`eth_sendUserOperation`と同じエラーコードが返されます。
また、コントラクトアカウントの内部処理が`revert`した場合や、ペイマスターの`postOp`処理が`revert`した場合にもエラーになります。  

## eth_getUserOperationByHashとは？

`eth_getUserOperationByHash`は、`eth_sendUserOperation`のレスポンスとして返された`userOpHash`を使って、対応する**UserOperation**の情報を取得するためのメソッドです。  

このメソッドに`userOpHash`を渡すと、以下のような動作をします。 

- その**UserOperation**がすでにブロックに含まれている場合、`entryPoint`のアドレス、`blockNumber`、`blockHash`、`transactionHash`を含む完全な**UserOperation**データを返します。  
- まだブロックに含まれておらず、bundlerのmempoolにある場合、完全な**UserOperation**データが返される可能性がありますが、`blockNumber`、`blockHash`、`transactionHash`の値は`null`になります。  
- それ以外（例えば、メモリプールから削除された場合）は、`null`を返します。  

## eth_getUserOperationReceiptとは？

`eth_getUserOperationReceipt`は、`userOpHash`をもとに**UserOperation**の実行結果（レシート）を取得するメソッドです。  

**UserOperation**がまだブロックに含まれていない場合は`null`が返ります。

含まれている場合は、以下の情報が取得できます。  
- `userOpHash`
    - **UserOperation**のハッシュ。
- `entryPoint`
    - 処理が行われた**EntryPoint**コントラクトアドレス。  
- `sender`
    - **UserOperation**を送信したアドレス。  
- `nonce`
    - **UserOperation**の実行に使われた`nonce`。
- `paymaster`
    - 使用されたペイマスター（存在しない場合は空）。  
- `actualGasCost`
    - 実際に消費されたガスコスト（アカウントまたはペイマスターが負担）。 
- `actualGasUsed`
    - **UserOperation**全体で使用されたガス量（事前検証、作成、検証、実行を含む）。
- `success`
    - 実行が成功したかどうか（`true`なら成功、`false`なら失敗）。  
- `reason`
    - **UserOperation**が`revert`した場合のエラーメッセージ（バイト配列）。  
- `logs`
    - この**UserOperation**によって生成されたログ（同じバンドル内の他の**UserOperation**のログは含まれない）。  
- `receipt`
    - トランザクションのレシート（これはバンドル全体のものであり、単体の**UserOperation**専用ではない）。  

## eth_supportedEntryPointsとは？

`eth_supportedEntryPoints`は、クライアントがサポートする**EntryPoint**コントラクトのアドレスを取得するメソッドです。  

このメソッドを呼び出すと、配列としてサポートされている**EntryPoint**コントラクトのアドレスを返します。
配列の最初の要素が、クライアントが推奨する**EntryPoint**コントラクトのアドレスになります。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_supportedEntryPoints",
  "params": []
}
```

例えば、上記のリクエストに対するレスポンスは以下のようになります。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    "0xcd01C8aa8995A59eB7B2627E69b40e0524B5ecf8",
    "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6"
  ]
}
```  

この場合、`0xcd01C8aa8995A59eB7B2627E69b40e0524B5ecf8`が推奨**EntryPoint**となります。  

## eth_chainIdとは？ 

`eth_chainId`は、現在のネットワークの**EIP155**で提案されているChain IDを取得するメソッドです。 

:::note info
**EIP155**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/2526432899353e4a0225

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_chainId",
  "params": []
}
```

例えば、Ethereumメインネットの場合、上記のリクエストに対してのレスポンスは以下のようになります。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x1"
}
```  
この`0x1`は、EthereumメインネットのChain IDを示しています。
その他のチェーンでは異なる値が返されます。
例えば、Polygonなら`0x89`、Goerliテストネットなら`0x5`などになります。  

## debug Namespaceとは？  

`debug` 名前空間に含まれるJSON-RPCメソッドは、**ERC4337**のバンドラー（bundler）の動作をテストしやすくするためのデバッグ用APIです。
これらのメソッドは、テスト環境でのみ使用できるようになっており、本番環境では使用できないようにするべきです。
主に、互換性テストやデバッグ作業の効率化を目的として用意されています。  

## debug_bundler_clearStateとは？  

このメソッドは、バンドラーのメモリプールや、ペイマスター・アカウント・ファクトリーのレピュテーションデータをリセットします。
テスト中に状態をリセットしたいときに便利です。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_clearState",
  "params": []
}
```

上記のリクエストを送ると、成功時には`"ok"`が返ります。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "ok"
}
```

## debug_bundler_dumpMempoolとは？  

現在バンドラーのメモリプールにある**UserOperation**を一覧取得するメソッドです。  
**EntryPoint**コントラクトのアドレスを指定すると、その**EntryPoint**コントラクトで管理されている**UserOperation**のリストが返されます。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_dumpMempool",
  "params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"]
}
```

例えば、上記のリクエストに対して以下のようなレスポンスが返ってきます。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
        sender, // address
        nonce, // uint256
        factory, // address
        factoryData, // bytes
        callData, // bytes
        callGasLimit, // uint256
        verificationGasLimit, // uint256
        preVerificationGas, // uint256
        maxFeePerGas, // uint256
        maxPriorityFeePerGas, // uint256
        signature // bytes
    }
  ]
}
```

mempoolに入っている**UserOperation**の内容を確認することで、バンドラーが正しく動作しているかを検証できます。  

## debug_bundler_sendBundleNowとは？

通常、バンドラーは一定のルールに従って**UserOperation**をバンドルし、Ethereumネットワークに送信します。
しかし、このメソッドを使うと、現在メモリプールにある**UserOperation**を即座にバンドルして送信することができます。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_sendBundleNow",
  "params": []
}
```

上記のリクエストのレスポンスとして、送信されたバンドルのトランザクションハッシュが返されます。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0xdead9e43632ac70c46b4003434058b18db0ad809617bd29f3448d46ca9085576"
}
```

## debug_bundler_setBundlingModeとは？ 

バンドラーの動作モードを変更するメソッドです。  

- `auto` に設定すると、バンドラーは通常のルールに従って自動でバンドルを処理します。  
- `manual` に設定すると、明示的に`debug_bundler_sendBundleNow`を呼び出さない限り、バンドラーは**UserOperation**をバンドルしません。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_setBundlingMode",
  "params": ["manual"]
}
```

例えば、上記の以下のリクエストでモードを手動（manual）に切り替えられ、以下のレスポンスが取得できます。 

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "ok"
}
```

成功すると、`"ok"`が返ります。  

## debug_bundler_setReputationとは？  
特定のアドレスのレピュテーションデータを設定するメソッドです。  

- `opsSeen`
    - そのアドレスの**UserOperation**がメモリプールに追加された回数。  
- `opsIncluded`
    - そのアドレスの**UserOperation**がブロックチェーンに記録された回数。

これを使うと、テスト中に特定のアドレスのレピュテーションを操作し、異なる状況をシミュレーションできます。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_setReputation",
  "params": [
    [
      {
        "address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6",
        "opsSeen": "0x14",
        "opsIncluded": "0x0D"
      }
    ],
    "0x1306b01bC3e4AD202612D3843387e94737673F53"
  ]
}
```

例えば、上記のリクエストで特定のアドレスのレピュテーションを設定でき、以下のレスポンスが取得できます。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "ok"
}
```

成功すると、`"ok"`が返ります。  

## debug_bundler_dumpReputationとは？

現在バンドラーが記録しているすべてのアドレスのレピュテーションデータを取得するメソッドです。  
- `opsSeen`
    - **UserOperation**がメモリプールに追加された回数。 
- `opsIncluded`
    - **UserOperation**がブロックチェーンに記録された回数。
- `status`
    - アドレスの現在の状態（`ok` / `throttled` / `banned`）。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_dumpReputation",
  "params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"]
}
```

例えば、上記のリクエストに対して以下のようなレスポンスが返ります。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    { "address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6",
      "opsSeen": "0x14",
      "opsIncluded": "0x13",
      "status": "ok"
    }
  ]
}
```

アドレスのステータスが`throttled`や`banned`になっている場合は、ペイマスターやアカウントがレート制限を受けている可能性があるため調整が必要になります。  

## debug_bundler_addUserOpsとは？

通常、**UserOperation**はバンドラーによる検証を経てmempoolに追加されます。
しかし、このメソッドを使うと、**UserOperation**の検証をスキップして強制的にmempoolへ追加できます。  

テストの時に、特定の状態をシミュレーションするために使われることが多いです。  

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_bundler_addUserOps",
  "params": [
    [
      { sender: "0xa...", ... },
      { sender: "0xb...", ... }
    ]
  ]
}
```

例えば、以下のリクエストで複数の**UserOperation**を追加でき、以下のレスポンスを取得できます。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "ok"
}
```

成功すると、`"ok"`が返ります。  

# 補足

bundlerは、デバッグ機能を一式提供することが求められており、この機能を使用することで**ERC7769**の仕様に準拠しているかを確認できます。

# 互換性

**ERC7769**では、互換性の課題を引き起こさない新しいJSON-RPC APIとして定義しています。

# セキュリティ

## UserOperation mempoolのDoS攻撃対策  

**ERC4337**ノードを運用することは計算コストが高く、特に公開されている本番環境のノードはDoS攻撃の標的になりやすくなります。  

この問題への対策として、**ERC7562**の検証ルールが導入されています。
**ERC7562**では、各参加者のレピュテーション（信用度）をトラッキングする仕組みが定義されており、不正な**UserOperation**がmempoolに追加されるのを防ぐことができます。  

例えば、スパム的な**UserOperation**を大量に送信してノードを負荷で圧迫しようとする攻撃があった場合、**ERC7562**の仕組みによって悪意のあるアドレスを特定して制限やブロックを行うことが可能になります。  

そのため、本番環境で**ERC4337**ノードを運用する場合は、必ず**ERC7562**のルールを実装することが推奨されています。
このルールを導入することで、攻撃のリスクを最小限に抑えて安定した運用ができるようになります。  

## 本番環境でのデバッグAPIの無効化

**ERC4337**のデバッグAPIは、あくまで開発やテスト用の機能であり、本番環境では絶対に公開すべきではないです。  

デバッグAPIには、mempoolを直接操作したり、バンドラーの動作を強制的に変更したりする機能が含まれています。
そのため、万が一これが本番環境で開放されてしまうと、攻撃者がノードの動作を意図的に操作してシステム全体の安定性を損なう可能性があります。  

具体的なリスクとしては、以下のようなものが考えられます。  
- `debug_bundler_clearState` を悪用して、mempoolを強制的にクリアし、全ての**UserOperation**を削除する。
- `debug_bundler_sendBundleNow` を連続で呼び出し、意図しないタイミングでバンドルを送信させる。  
- `debug_bundler_setReputation` を操作し、特定のアカウントを不当にブロックまたは優遇する。 

そのため、本番環境ではデフォルトでデバッグAPIを無効化することが必須です。
もし何らかの理由でデバッグAPIを有効化する必要がある場合でも、システムに警告を表示してAPIが誤って公開されないように十分な対策を取るべきです。

# 引用

Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), Alex Forshtat (@forshtat), "ERC-7769: JSON-RPC API for ERC-4337 [DRAFT]," Ethereum Improvement Proposals, no. 7769, August 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7769.

# 最後に

今回は「**ERC4337**に準拠したコントラクトアカウントが、**UserOperation**をbundlerに送付するための新しいJSON-RPC APIメソッドを提案している**ERC7769**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4