はじめに

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

今回は、複数のオンチェーン呼び出しをウォレットからまとめて送信し、その実行状態まで一貫して扱える仕組みを提案しているEIPC5792についてまとめていきます!

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

EIPC5792は、ウォレットとアプリケーション(DApp)がやり取りするための新しいJSON-RPCメソッド群を定義しています。 JSON-RPCとは、Ethereumで一般的に使われている、アプリとウォレットやノードが通信するためのリモート呼び出しの仕組みです。

この仕様で定義される主な機能は以下の2つです。

1つ目は、アプリがウォレットに対して「複数のオンチェーン書き込み処理」をまとめて依頼できることです。 ここでいうオンチェーン書き込みとは、トランザクションを送信してブロックチェーンの状態を変更する処理を指します。 EIPC5792では、これらの処理を1件ずつではなく、バッチ(まとめた単位)としてウォレットに渡すことができます。

2つ目は、そのバッチ処理が今どの状態にあるのかを確認する仕組みを提供することです。 トランザクションが送信されたのか、まだ待機中なのか、成功したのか失敗したのかといった進捗を、アプリ側が明確に追跡できます。

また、アプリは単に処理を投げるだけでなく、「ウォレットが事前に表明している機能」を前提として処理を依頼できます。 ここでいうウォレットの機能とは、例えば以下のようなものです。

  • バッチトランザクションを1つのトランザクションとしてまとめて実行できるか
  • 特定の新しいトランザクション形式に対応しているか
  • ガス代を肩代わりする仕組み(後述するペイマスター)を使えるか

さらに、EIPC5792では、アプリがウォレットに対して「どのような機能をサポートしているのか」を問い合わせるための、新しい専用のウォレットRPCも定義しています。 これにより、アプリは実行前に「このウォレットでは何ができるのか」を安全に確認したうえで処理を組み立てられるようになります。

動機

現在使われているトランザクション送信方法の問題点

現在、ウォレットからトランザクションを送信し、その結果を確認するために一般的に使われているJSON-RPCメソッドは以下の2つです。

メソッド名 役割
eth_sendTransaction ウォレットにトランザクションの送信を依頼する
eth_getTransactionReceipt 送信済みトランザクションの結果を取得する

しかし、これらの仕組みは現代の開発ニーズに合わなくなっています。

まず、eth_sendTransactionという名前自体が、歴史的な背景を引きずっています。 この名前は「ノードがウォレットの役割も兼ねていた時代」の名残であり、現在のようにウォレットとノードの責務が明確に分離された世界観とは一致していません。

また、機能面でも大きな制約があります。

バッチ処理に対応できない問題

現代のDApp開発では、複数のコントラクト呼び出しをまとめて実行したいケースが増えています。 特にスマートアカウント(コントラクトウォレット)では、複数の処理を1つのトランザクションとしてまとめ、すべて成功するか、すべて失敗するかを保証する「アトミック実行」が一般的です。

しかし、eth_sendTransactionは「1回のRPCにつき1トランザクション」しか扱えません。 そのため、以下のようなことができません。

  • 複数の処理をまとめてウォレットに依頼する
  • それらを1つのトランザクションとして実行する前提で設計する

結果として、アプリ側で無理な分割や複雑な状態管理が必要になります。

新しいトランザクション形式を扱えない問題

Ethereumでは進化が続いており、新しいトランザクション形式も登場しています。 代表的な例がERC4337で導入されたアカウント抽象化です。

ERC4337では、ペイマスター(Paymaster)という仕組みを使えます。 ペイマスターとは、ユーザー本人ではなく第三者がガス代を支払うことを可能にする仕組みです。 これにより、ユーザーはETHを直接持っていなくてもDAppを利用できるようになります。

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

しかし、eth_sendTransactionには以下のような制約があります。

  • ペイマスターの指定方法がない
  • 新しいトランザクション構造を表現できない
  • 将来の拡張を前提とした設計になっていない

つまり、新しい仕組みを使いたくても、RPCレベルで表現できないという問題があります。

ウォレットとアプリの責務を明確に分けたいという要求

EIPC5792が目指している大きな方向性の1つは、「ウォレットとアプリの役割をきれいに分離すること」です。

  • アプリは「どのような処理をしたいか」を宣言する
  • ウォレットは「その処理をどう実行するか」を判断する

この分離ができていないと、アプリはウォレットの内部実装や制約を過度に意識する必要が出てしまいます。

将来の拡張を安全に行うための仕組み

もう1つの重要な動機は、将来の機能追加を安全かつ段階的に行えるようにすることです。

EIPC5792では、ウォレットが対応している機能を事前に問い合わせる仕組みを用意しています。 これにより、以下のようなことが可能になります。

  • アプリが未対応機能を誤って使ってしまうのを防ぐ
  • 新機能を段階的に追加しても、既存アプリとの互換性を保てる
  • ウォレットとアプリ間での事前調整を最小限に抑えられる

結果として、エコシステム全体がより安全に、かつ柔軟に進化できるようになります。

仕様

EIPC5792では、新しく4つのJSON-RPCメソッドが定義されています。

このうち3つは「オンチェーン書き込み処理をバッチで扱うためのRPC」、残り1つは「ウォレットが対応している機能(ケイパビリティ)を問い合わせるためのRPC」です。

アプリケーションは、これら3つのバッチ系RPCを優先的に使い、もしウォレットが未対応であれば、従来の eth_sendTransaction や eth_getTransactionReceipt にフォールバックする想定になっています。 つまり、後方互換性を保ちながら段階的に移行できる設計です。

wallet_sendCalls

基本的な役割

wallet_sendCalls は、アプリがウォレットに対して「複数のオンチェーン呼び出しをまとめて送信してほしい」と依頼するためのRPCです。

従来のように1トランザクションずつ送るのではなく、以下の情報を1回のRPCでまとめて渡します。

  • どのアドレスから送信するか
  • どのチェーンで実行するか
  • 実行したい複数の呼び出し内容
  • アトミックに実行する必要があるかどうか
  • ウォレット固有の追加機能に関する指定

チェーンIDとアドレスの指定方法

from と chainId は EIP155 に基づく形式で指定されます。

  • 16進数表記
  • 0x プレフィックス付き
  • chainId に先頭ゼロは不可

これはEthereumで一般的に使われているチェーン識別方法と同じです。

calls フィールドの構造

calls は、実行したい処理を配列で指定します。 各要素は以下のような単純な構造です。

フィールド 説明
to 呼び出し先アドレス
data コントラクト呼び出しデータ
value 送金するETH量(16進数)
capabilities この呼び出し専用の追加指定

これにより、ETH送金とコントラクト呼び出しを混在させたバッチも作れます。

capabilities の考え方

capabilities とは何か

capabilities は、アプリとウォレットが「追加機能について合意するための仕組み」です。

例えばERC4337のようなアカウント抽象化ウォレットでは、ペイマスターという仕組みを使ってガス代を第三者が支払えます。 その場合、アプリは「どのペイマスターを使うか」という情報をウォレットに伝える必要があります。

その指定を行う場所が capabilities です。

グローバル指定と呼び出し単位指定

capabilities は2か所に書けます。

  • リクエスト全体に適用されるもの
  • 個々の call にだけ適用されるもの

これにより、「基本は共通設定だが、この呼び出しだけ特別な条件を付けたい」といった柔軟な指定が可能になります。

optional フラグの意味

各 capability には optional フラグを付けられます。

optional 意味
true 非対応でも処理を続行してよい
false / 未指定 非対応ならウォレットは必ず拒否

この仕組みによって、アプリは「使えたら便利だが必須ではない機能」を安全に指定できます。

atomicRequired の意味と挙動

アトミック実行とは

アトミック実行とは、「すべて成功するかすべて失敗するか」のどちらかになる実行方式です。 途中まで成功して一部だけ失敗する、という状態は発生しません。

atomicRequired が true の場合

アプリが atomicRequired: true を指定した場合、ウォレットには厳しい要件が課されます。

  • すべての呼び出しをアトミックに実行しなければならない
  • 呼び出しの途中に、他のトランザクションや呼び出しが割り込んではならない
  • ウォレットがアトミック実行に未対応の場合、対応可能であれば事前にアップグレードしなければならない

この条件を満たせない場合、ウォレットは処理を拒否する必要があります。

atomicRequired が false の場合

atomicRequired: false の場合は、ウォレットに裁量が与えられます。

  • 単純に順番に実行してもよい
  • 対応していればアトミック実行してもよい
  • アトミック対応へアップグレードしてから実行してもよい

この指定は「アトミック実行が必須ではない」ことを意味します。

ウォレットが守るべき基本ルール

wallet_sendCalls を受け取ったウォレットは、以下のルールを必ず守る必要があります。

項目 内容
実行順序 calls に指定された順序を守る
チェーン 指定された chainId のみで実行
送信元 from が指定されていれば必ずそれを使用
from 未指定時 ユーザーに選択させる機会を提供
ユーザー拒否 拒否されたら一切送信しない
未対応 capability optional でなければ必ず拒否

また、ウォレットは「すべての呼び出しが確定するまで待つ」ような挙動をしてはいけません。

IDの取り扱いルール

wallet_sendCalls では、バッチを識別するための id が使われます。

  • 文字列型
  • 最大4096バイト
  • アプリ側で指定しても、ウォレットが生成してもよい

アプリが指定する場合、以下を満たす必要があります。

  • 同一アプリ・同一送信者内で一意であること
  • アプリはドメイン単位で識別される想定

ウォレットは重複したIDを含むリクエストを必ず拒否します。

wallet_sendCalls RPC定義

ts
type Capability = {
  [key: string]: unknown;
  optional?: boolean;
}

type SendCallsParams = {
  version: string;
  id?: string;
  from?: `0x${string}`;
  chainId: `0x${string}`;
  atomicRequired: boolean;
  calls: {
    to?: `0x${string}`;
    data?: `0x${string}`;
    value?: `0x${string}`;
    capabilities?: Record<string, Capability>;
  }[];
  capabilities?: Record<string, Capability>;
};

type SendCallsResult = {
  id: string;
  capabilities?: Record<string, any>;
};

wallet_sendCalls の使用例

リクエスト例
json
[
  {
    "version": "2.0.0",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "chainId": "0x01",
    "atomicRequired": true,
    "calls": [
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x9184e72a",
        "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
      },
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x182183",
        "data": "0xfbadbaf01"
      }
    ],
    "capabilities": {
      "paymasterService": {
        "url": "https://...",
        "optional": true
      }
    }
  }
]

ここではペイマスター指定が optional: true なので、非対応ウォレットでも処理は続行されます。

レスポンス例
json
{
  "id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}

wallet_getCallsStatus の概要

基本的な役割

wallet_getCallsStatus は、wallet_sendCalls で送信したバッチが「今どうなっているか」を確認するためのRPCです。

返されるレシート情報は、eth_getTransactionReceipt のサブセットになっています。

atomic フィールドと receipts の関係

このRPCでは、ウォレットがどのように実行したかを atomic フィールドで明示します。

atomic receipts の内容
true 1件またはまとめられたレシート
false 実行されたすべてのトランザクションのレシート配列

順序は必ずオンチェーンに含まれた順です。

logs の制限

receipts.logs には、以下のみが含まれます。

  • wallet_sendCalls によって作られた処理に直接関係するログ

例えばERC4337のバンドラーが複数のユーザー操作をまとめた場合でも、他ユーザーのログは含まれてはいけません。 また、ウォレットのアップグレード処理など、間接的なログも除外されます。

wallet_getCallsStatus

基本的な役割

wallet_getCallsStatus は、wallet_sendCalls で送信した「呼び出しバッチ」が現在どの状態にあるのかを取得するためのRPCです。

このRPCは単にトランザクションレシートを返すだけではなく、「まだ実行中なのか」、「成功したのか」、「失敗したのか」といったオフチェーンの状態も含めた要約情報を返します。

そのため、従来の eth_getTransactionReceipt よりも、アプリ側での状態管理がしやすくなっています。

wallet_getCallsStatus RPC仕様

パラメータ構造
ts
type GetCallsParams = [string];

引数は1つだけで、wallet_sendCalls のレスポンスで返されたバッチ識別子(id)を指定します。 このIDは64バイトの値を16進数文字列として表現したものです。

レスポンス構造
ts
type GetCallsResult = {
  version: string;
  id: `0x${string}`;
  chainId: `0x${string}`;
  status: number;
  atomic: boolean;
  receipts?: {
    logs: {
      address: `0x${string}`;
      data: `0x${string}`;
      topics: `0x${string}`[];
    }[];
    status: `0x${string}`;
    blockHash: `0x${string}`;
    blockNumber: `0x${string}`;
    gasUsed: `0x${string}`;
    transactionHash: `0x${string}`;
  }[];
  capabilities?: Record<string, any>;
};

それぞれのフィールドの意味を整理します。

フィールド 説明
version RPCレスポンスのバージョン
id 呼び出しバッチの識別子
chainId 実行されたチェーンID
status バッチ全体の状態を表すステータスコード
atomic 実際にアトミック実行されたかどうか
receipts オンチェーンに含まれたトランザクションのレシート
capabilities ウォレットが付加した能力関連の追加情報

receipts は、eth_getTransactionReceipt の完全なコピーではなく、 必要な情報だけに絞ったサブセットである点が重要です。

status フィールドの考え方

status フィールドの目的

status は、呼び出しバッチ全体の「今の状態」を一目で分かるようにするための数値です。

これはオンチェーン情報だけでは判断できない、「ウォレット内部でどう扱われているか」という文脈を補う役割を持っています。

ステータスコードの分類

ステータスコードは大きく以下のカテゴリに分かれます。

カテゴリ 意味
1xx 処理中(未確定)
2xx 成功してオンチェーンに確定
4xx オフチェーンでの失敗
5xx チェーンルールに基づく失敗

定義されているステータスコード

コード 説明
100 ウォレットは受信したが、まだオンチェーン実行が完了していない
200 すべて成功し、オンチェーンに確定した
400 オンチェーンに含まれず、ウォレットは再試行しない
500 完全にリバートされ、ガス関連以外の変更は残らなかった
600 部分的にリバートされ、一部の変更がオンチェーンに残った

より詳細なコードは、将来的に別のERCで定義される想定です。

wallet_getCallsStatus の使用例

リクエスト例
json
[
  "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
]
レスポンス例
json
{
  "version": "2.0.0",
  "chainId": "0x01",
  "id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
  "status": 200,
  "atomic": true,
  "receipts": [
    {
      "logs": [
        {
          "address": "0xa922b54716264130634d6ff183747a8ead91a40b",
          "topics": [
            "0x5a2a90727cc9d000dd060b1132a5c977c9702bb3a52afe360c9c22f0e9451a68"
          ],
          "data": "0xabcd"
        }
      ],
      "status": "0x1",
      "blockHash": "0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a",
      "blockNumber": "0xabcd",
      "gasUsed": "0xdef",
      "transactionHash": "0x9b7bb827c2e5e3c1a0a44dc53e573aa0b3af3bd1f9f5ed03071b100bb039eaff"
    }
  ]
}

この例では、バッチはアトミックに実行され、すべて成功していることが分かります。

wallet_showCallsStatus

基本的な役割

wallet_showCallsStatus は、指定した呼び出しバッチについて、ウォレットのUI上でユーザーに情報を表示させるためのRPCです。 このRPCはデータを返しません。

挙動の特徴

  • 正常なIDが指定された場合、ウォレットはUI表示を行う
  • IDが不明、または処理できない場合はRPCエラーを返す

アプリ側は、このRPCを使って「詳細を見る」ボタンのような導線を提供できます。

RPC仕様と使用例

ts
type ShowCallsParams = string;
json
["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"]

wallet_getCapabilities

基本的な役割

wallet_getCapabilities は、ウォレットがどのような機能をサポートしているかを、事前の個別交渉なしに取得するためのRPCです。

例として、以下のような能力を問い合わせられます。

  • バッチトランザクション対応
  • ペイマスター対応(ERC4337)
  • セッションキー対応

認可に関するルール

アプリがまだユーザーから接続許可を得ていない場合、 ウォレットは4100 Unauthorizedエラーを返すべきとされています。

capabilities の取得方法と優先順位

capabilities は、以下のような方法で取得・共有される可能性があります。

  • wallet_getCapabilities による直接取得
  • CAIP25に準拠したセッション情報
  • EIP6963の rdns 識別子から導出されるURL

CAIP25については以下の記事を参考にしてください。

ただし、これらの情報に食い違いがある場合、ライブRPCのレスポンスが常に正として扱われます。

wallet_getCapabilities RPC仕様

パラメータ構造
ts
type GetCapabilitiesParams = [
  `0x${string}`,
  [`0x${string}`]?
];
  • 第1引数はウォレットアドレス
  • 第2引数は問い合わせたいチェーンIDの配列(省略可能)
レスポンス構造
ts
type GetCapabilitiesResult =
  Record<`0x${string}`, Record<string, any>>;

capabilities はチェーンごとに分かれて返されます。

チェーンIDごとのルール

ルール 説明
0x0 全チェーン共通の能力
個別chainId チェーン固有の能力
非対応チェーン エラーにせず、単に含めない

wallet_getCapabilities の使用例

リクエスト例
json
[
  "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
  ["0x2105", "0x14A34"]
]
レスポンス例
json
{
  "0x0": {
    "flow-control": {
      "supported": true
    }
  },
  "0x2105": {
    "paymasterService": {
      "supported": true
    },
    "sessionKeys": {
      "supported": true
    }
  },
  "0x14A34": {
    "auxiliaryFunds": {
      "supported": true
    }
  }
}

このように、チェーンごとに対応状況が異なることが明確に分かります。

atomic Capability

atomic Capabilityの役割

atomic Capabilityは、wallet_sendCalls を通じて要求されたバッチトランザクションを、ウォレットがどのように実行できるかを表すための能力定義です。

ここでいう「atomic」とは、単に複数の呼び出しをまとめることではなく、以下の2つを同時に満たすことを意味します。

  • すべて成功するか、すべて失敗するかのどちらかになること(原子性)
  • 途中に他のトランザクションや呼び出しが割り込まないこと(連続性)

この capability は、wallet_getCapabilities を通じてアプリが事前に確認できます。

atomic Capabilityのステータス定義

atomic Capabilityには status という1つのプロパティだけがあり、その値によってウォレットの対応状況が表されます。

status 意味
supported バッチをアトミックかつ連続的に実行できる
ready ユーザー承認があれば supported にアップグレード可能
unsupported アトミック実行も連続実行も保証しない
supported の意味

status: supported の場合、そのチェーンにおいてウォレットは以下を保証します。

  • すべての呼び出しを1つの論理単位として実行する
  • 途中で失敗した場合、オンチェーンに意味のある変更を残さない
  • 他のトランザクションが途中に挟まることがない

この状態では、atomicRequired: true なリクエストも問題なく処理できます。

ready の意味

status: ready は、「今は未対応だがユーザーが承認すれば対応可能」という状態です。

例えば以下のようなケースを想定しています。

  • ウォレットの内部ロジックをアップグレードする必要がある
  • スマートアカウントへの移行が必要
  • セキュリティ上、明示的なユーザー確認が必要

この場合、atomicRequired: true が指定されると、ウォレットはアップグレードを提案し、 ユーザーが拒否した場合はエラーになります。

unsupported の意味

status: unsupported の場合、そのウォレットは以下を意味します。

  • バッチをアトミックに実行できない
  • 連続実行の保証もしない
  • アップグレードの提案もしない

この状態で atomicRequired: true が指定された場合、ウォレットは必ずリクエストを拒否します。

チェーンごとの capability と省略時の意味

atomic Capabilityはチェーンごとに独立して定義されます。 つまり、あるチェーンでは supported でも、別のチェーンでは unsupported ということがあり得ます。

また、特定のチェーンに対してatomic Capabilityがそもそも存在しない場合、そのチェーンではバッチ処理自体をサポートしていないと解釈されます。

ただし、他の capability によって明示的に上書きされている場合は、その定義が優先されます (そのような capability はEIPC5792のスコープ外です)。

atomic Capabilityの型定義

ts
type AtomicCapability = {
  status: 'supported' | 'ready' | 'unsupported';
};

atomic Capabilityを含む wallet_getCapabilities の例

json
{
  "0x2105": {
    "atomic": {
      "status": "supported"
    }
  },
  "0x14A34": {
    "atomic": {
      "status": "unsupported"
    }
  }
}

この例では、0x2105 チェーンではアトミック実行が可能ですが、 0x14A34 チェーンでは不可能であることが分かります。

エラーコード

EIPC5792では、定義されているRPCメソッド専用のエラーコードがいくつか追加されています。 これらはJSON-RPC 2.0の仕様に従い、code と message を持つエラーオブジェクトとして返されます。

エラーコード一覧と意味

以下は、定義されているすべてのエラーコードです。

コード メッセージ 説明 関連RPC
-32602 Invalid params パラメータ形式が不正。0xプレフィックス不足やスキーマ不一致など sendCalls / getCallsStatus / showCallsStatus / getCapabilities
4001 User Rejected Request ユーザーがバッチ送信を拒否した wallet_sendCalls
4100 Unauthorized 指定アドレスが未接続、またはウォレットに存在しない wallet_sendCalls / wallet_getCapabilities
5700 Unsupported non-optional capability optional でない capability が未対応 wallet_sendCalls
5710 Unsupported chain id 指定されたチェーンIDに非対応 wallet_sendCalls
5720 Duplicate ID 同じIDのバッチがすでに存在する wallet_sendCalls
5730 Unknown bundle id 指定されたバッチIDが存在しない wallet_getCallsStatus / wallet_showCallsStatus
5740 Bundle too large バッチサイズが大きすぎる wallet_sendCalls
5750 Atomic-ready wallet rejected upgrade atomic 対応アップグレードをユーザーが拒否 wallet_sendCalls
5760 Atomicity not supported アトミック実行必須だがウォレットが未対応 wallet_sendCalls

atomic Capabilityとエラーコードの関係

atomic Capabilityとエラーコードは密接に関係しています。

例えば以下のような流れになります。

  • atomicRequired が true
  • walletのatomic statusが ready
  • ユーザーがアップグレードを拒否

この場合、ウォレットは 5750 を返します。 一方で、status が unsupported の場合は、即座に 5760 が返されます。

補足

命名について

まず、eth_sendTransaction を拡張して新機能を載せる案が検討されていますが、見送られています。 理由は、eth_sendTransaction という名前自体が「ノードがトランザクション署名をしていた時代」の名残で、いまの「ウォレットが署名してアプリが依頼する」という役割分担に合っていないからです。 そこで、用途が分かりやすい wallet_ 名前空間のRPCへ移行する方針になっています。

次に、名前の候補として wallet_sendTransaction、wallet_sendCalls などが検討されています。最終的に wallet_sendCalls が選ばれた理由は、ウォレットの種類によって「呼び出し」と「トランザクション」の対応関係が変わるからです。

候補名 採用しなかった理由
wallet_sendTransaction EOA(外部所有アカウント)のウォレットでは、wallet_send* が結果として複数トランザクションを送る可能性があり、「Transaction」という単数が誤解を招く
wallet_sendTransactions ERC4337のような実装では、複数の call が結果として「1つのトランザクション(または同等のオンチェーン単位)」にまとまることがあり、「Transactions」という複数形が誤解を招く
wallet_sendCalls 複数の call を受け取り、ウォレット実装に応じて「1つにも複数にもなり得る」ことを名前が自然に表現できるため採用

ここでいうEOAは、秘密鍵で直接署名する通常のアカウントを指します。 スマートアカウントはコントラクトで制御されるアカウントで、ERC4337が代表例です。

呼び出しのアトミック実行

wallet_sendCalls は calls 配列を受け取りますが、「必ず1つのトランザクションとして実行しなければならない」とはしていません。 これは意図的です。

理由は、ウォレットの種類で実行方法が変わるからです。 EOAウォレットは複数 call を1トランザクションにまとめられる場合もあれば、複数トランザクションとして送る実装もあり得ます。 そこで、ウォレット側は「自分がどの程度の保証でバッチを実行できるか」を表明し、アプリ側は「最低限必要な保証」を要求できるようにしています。

このために、atomic capability(特別な capability)が仕様の中心要素として組み込まれています。 曖昧さを減らすために、アトミック要件と実際の扱いは次の2点で明示されます。

  • wallet_sendCalls の atomicRequired(アプリが要求する最低要件)
  • wallet_getCallsStatus の atomic(ウォレットが実際にどう実行したか)

当初は「複数 call は必ずアトミックに実行する」を提案していましたが、意見の分かれる設計になりやすく、ウォレット実装の自由度を潰す方向になりやすいという判断で撤回されています。 代わりに atomic capabilityを定義することで、EOAウォレットが複数 call を受け付けられる余地を残しつつ、開発者が「アトミック実行される場合だけバッチ送信したい」という選択もできるようにしています。

理解しやすいように、アプリとウォレットの関係を図にします。

sequenceDiagram
  participant App as アプリ
  participant Wallet as ウォレット
  participant Chain as チェーン

  App->>Wallet: wallet_getCapabilities(atomic statusを確認)
  Wallet-->>App: supported / ready / unsupported

  App->>Wallet: wallet_sendCalls(atomicRequired=true/false)
  alt atomicRequired=true かつ supported
    Wallet->>Chain: まとめて実行(アトミック・連続)
  else atomicRequired=true かつ ready
    Wallet->>Wallet: アップグレード提案(ユーザー承認待ち)
    alt 承認
      Wallet->>Chain: まとめて実行(アトミック・連続)
    else 拒否
      Wallet-->>App: エラー(アップグレード拒否)
    end
  else atomicRequired=true かつ unsupported
    Wallet-->>App: エラー(アトミック非対応)
  else atomicRequired=false
    Wallet->>Chain: 1つまたは複数に分けて実行(裁量)
  end

  App->>Wallet: wallet_getCallsStatus(結果確認)
  Wallet-->>App: status / atomic / receipts

callごとのガスリミット(Call Gas Limit)

当初は、calls の各要素に gas フィールド(call単位のガス上限)を持たせる案がありました。しかし、これは誤解を生むとして撤回されています。

理由は、ウォレット実装によって「call単位のガス指定」が成立しないケースがあるからです。 特に ERC4337 では、callごとではなく「ユーザーオペレーション全体」に対してガス制限が決まります。 つまり、call 単位でガス上限を指定できません。

そこで次に、「全 call に共通で適用される単一の gas 値」を提案しました。 これは ERC4337には合いますが、EOAウォレットには合いません。 EOAが複数 call を複数トランザクションで送る場合、トランザクションごとにガス制御の考え方が変わり、単一の gas 値が現実の実装に合わなくなります。

結果として、ユースケースを横断して一貫した意味を持たせるのが難しいため、ガス指定そのものを仕様から削除しています。

互換性

新しい wallet_ RPC群をサポートしないウォレットは、これらのメソッドが呼ばれたときにエラーを返すべきです。

アプリ側のフォールバックは2つの方針が許容されています。

アプリの対応 内容
直列送信にフォールバック wallet_sendCalls が使えない場合、同じcall群を eth_sendTransaction で1件ずつ送る(バッチの意図を直列に分解して実行する)
非対応としてユーザーに通知 「このウォレットは未対応なので処理できなかった」とユーザーに示し、リクエストを処理しない

どちらを選ぶかはアプリ次第で、必ずフォールバックしなければならないとは書かれていません。

セキュリティ

アプリは「1トランザクションで送られる」と決めつけてはいけない

atomicRequired や atomic の値に関係なく、アプリ開発者は「必ず1つのトランザクションにまとまる」と仮定してはいけません。 例として、reorg(再編成)に強いL2が sendBundle のような機能を提供するケースが挙げられています。

reorg(再編成)は、ブロックチェーンで一時的に採用されたブロックが後から別のブロックに置き換わる現象です。 これに強い設計のL2では、オンチェーンの載せ方が独特になる可能性があり、アプリ側の「1トランザクション前提」は壊れやすいという意味です。

バッチ識別子(id)は推測できないようにする

ウォレットは、wallet_sendCalls のレスポンスで返すバッチ識別子(id)が予測可能にならないようにしなければなりません。 予測可能だと、悪意あるアプリが「他ユーザーの取引状況」を推測する足がかりを得る可能性があるからです。

ここで重要なのは、idが「順番に増える」、「特定パターンを持つ」といった性質を避けることです。

wallet_getCallsStatus の capabilities で機密情報を漏らさない

wallet_getCallsStatus のレスポンスには capabilities が含まれ得ますが、ウォレットはここでセンシティブな情報を漏らしてはいけません。 例えば、内部のリトライ戦略やユーザー固有の状態が推測できるような情報を返すと、攻撃や追跡に繋がる可能性があります。

プライバシー考慮

ここで言っていることの中心は、「capability の問い合わせが、ユーザーやウォレット実装の特定につながらないようにする」という点です。

段階的な許可と同意を守る

近年のUXでは、最初から全部許可を求めるのではなく、必要になったタイミングで少しずつ許可や同意を求めるやり方が一般的です。 これはユーザー体験を良くするだけでなく、ユーザーの匿名性の維持にも役立ちます。

その一方で、機能発見(feature discovery)を強くすると、「このウォレットは何ができるか」を過剰に露出させる動機が働きやすくなります。 そこで、capability の仕組みを使い、「機能が未対応なのか、許可がないだけなのか」を設計上わざと分かりにくくしています。 これにより、問い合わせ結果だけからユーザーの状態や許可状況を推測しにくくします。

過剰な capability 公開はフィンガープリントにつながる

ウォレットは、信頼できない相手に対して capabilities をむやみに公開しないことが推奨されています。 理由は、capabilities の組み合わせがウォレット実装やクライアント(ユーザーエージェント)固有の特徴になりやすく、「フィンガープリント」に使われる可能性があるからです。

フィンガープリントとは、ブラウザや拡張機能の挙動・対応機能などの特徴を組み合わせて、個人や同一クライアント集団を確率的に特定する手法です。 Webには他にも匿名性を崩す手段が存在するため、capability の過剰公開はそれらと組み合わさって、個人の特定や「特定ウォレットユーザー全体」の特定につながり得ます。

同様に、アプリ側が capabilities を過剰に問い合わせたり、第三者も含めて capability を過剰共有させるような設計は、避けるべきアンチパターンとされています。

引用

Moody Salem (@moodysalem), Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack), Dror Tirosh (@drortirosh), Jake Moxey (@jxom), Derek Rein (@arein), Alex Forshtat (@forshtat), Sam Wilson (@SamWilsn) sam@binarycake.ca, Borislav Itskov (@Oxbobby), Joao Tavares (@cryptotavares), Adam Fuller (@azf20), Philip Liao (@phil-ociraptor), bumblefudge (@bumblefudge), "EIP-5792: Wallet Call API," Ethereum Improvement Proposals, no. 5792, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5792.

最後に

今回は「複数のオンチェーン呼び出しをウォレットからまとめて送信し、その実行状態まで一貫して扱える仕組みを提案しているEIPC5792」についてまとめてきました! いかがだったでしょうか?

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

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