wallet_sendCallsとERC-7821|batch call実装と検証
EIP-5792のwallet_sendCallsとERC-7821のexecuteを分け、capability確認、atomic batch、status、revertをwagmi・viem・Foundryで検証します。

複数のcontract callを一度にwalletへ渡しても、一つの確認表示、一つのtransaction、atomic execution、gas sponsorが同時に保証されるわけではありません。EIP-5792はwalletへ複数callを依頼して結果を追うRPCを定め、ERC-7821はaccountが複数callを実行する最小interfaceを定めます。
この記事では、wallet側のwallet_getCapabilities、wallet_sendCalls、wallet_getCallsStatusと、account側のexecute(bytes32,bytes)を分けます。そのうえで、2-call成功、二つ目のcallによる全体revert、atomic非対応wallet、chain mismatchをローカル環境で再現します。
基準日は2026年8月30日です。EIP-5792はFinal、ERC-7821はDraftです。実在wallet、public RPC、deployed account、実資産は使わず、特定walletの対応状況は断定しません。
最初にwalletとaccountの二層へ分ける
同じ「batch call」でも、入口と実行場所は別です。
| 層 | 標準 | 主な役割 | この層だけでは保証しないこと |
|---|---|---|---|
| wallet RPC | EIP-5792 | capabilityを返す、ordered callsを受け取る、実行経路を選ぶ、statusとreceiptを返す | accountがERC-7821を使うこと、一つのtransaction、gas sponsor |
| account executor | ERC-7821 | modeとexecutionDataを検証し、account contextからcall列を実行する |
wallet UI、RPCの対応、どの認可経路で呼ばれたか |
EIP-5792対応walletは、EOAから複数transactionを送る、EIP-7702の委任先を使う、ERC-4337 smart accountへUserOperationを送るなど、accountに応じた経路を選べます。EIP-5792は、その内部経路をERC-7821へ固定していません。
ERC-7821も、walletから直接呼ばれることを前提にしません。owner、account自身、ERC-4337のEntryPointなど、誰にexecuteを許すかはaccount実装が決めます。
EIP-7702でEOAのaddressとstorageを保ちながらcodeを委任する仕組みはEIP-7702のdelegation確認手順、ERC-4337のEntryPointとUserOperationの失敗境界はUserOperationの失敗解析で詳しく分けています。
atomicは四つの意味を混ぜない
atomicという語を見たら、次の四つを別々に確認します。
- walletは要求したchainとaccountでatomic batchを扱えるか
- requestはatomic executionを必須にしたか
- 実行されたcall列は、一つのcallが失敗したとき全体を戻すか
- statusは実際の結果をatomicとして報告したか
wallet_sendCallsのatomicRequired: trueは、walletに「atomicかつ連続した実行ができなければrequestを拒否する」と求めます。これは、walletが必ず一つのtransactionを作るという指定ではありません。
また、gas sponsorは別のcapabilityです。atomicRequired: trueから、paymaster利用、手数料無料、利用者以外のgas負担を推測してはいけません。
ERC-7821のsingle-batch modeは、途中のcallが失敗するとrevert dataを外へ返し、それまでのstate changeも同じ実行の中で取り消します。account側のこの性質と、walletが返すatomic fieldを対応させて確認します。
capabilityをchainごとに先に取得する
送信前にwallet_getCapabilitiesを呼び、接続accountと対象chainの組み合わせを固定します。wagmi Core 3.4.0ではgetCapabilitiesから取得できます。
import { getCapabilities } from '@wagmi/core'
const capabilities = await getCapabilities(config, {
account: smartAccount,
chainId: 31337,
connector,
})
console.log(capabilities.atomic?.status)
// "supported"、"ready"、"unsupported"などをwalletのresponseどおり保存する
確認するのは、単にatomic keyが存在するかではありません。
| 項目 | 保存する値 | 理由 |
|---|---|---|
| wallet | 製品名、Extension / Mobile、version、確認日 | 対応は更新で変わり得る |
| account | addressとaccount種別 | 同じwalletでもaccountごとにcapabilityが異なり得る |
| chain | chainId |
capabilityはchainごとに返る |
| atomic | raw objectとstatus |
booleanへ丸めると準備状態を失う |
| その他 | paymaster、permissionsなどのraw capability | atomicとgas sponsorを分ける |
この記事のローカルwalletは、対応側で{ atomic: { status: "supported" } }、非対応側で{ atomic: { status: "unsupported" } }を返します。これはRPCの分岐を再現するための値で、実在walletの対応証拠ではありません。
wallet_sendCalls v2のrequestを固定する
viem 2.56.0のsendCallsは、versionを省略すると2.0.0を使い、forceAtomic: trueをRPCのatomicRequired: trueへ変換します。
import { sendCalls } from '@wagmi/core'
const { id } = await sendCalls(config, {
account: smartAccount,
chainId: 31337,
connector,
forceAtomic: true,
calls: [
{ to: target, data: encodeFunctionData({ abi, functionName: 'add', args: [2n] }) },
{ to: target, data: encodeFunctionData({ abi, functionName: 'add', args: [3n] }) },
],
})
walletへ届く主要fieldは次の形です。
{
"version": "2.0.0",
"chainId": "0x7a69",
"from": "0xSmartAccount",
"atomicRequired": true,
"calls": [
{ "to": "0xTarget", "data": "0x..." },
{ "to": "0xTarget", "data": "0x..." }
]
}
callの順序は意味を持ちます。二つ目が一つ目のstateに依存するなら、並べ替えられない前提をwalletとaccountの両方で守る必要があります。
idは、後でstatusを取得するための識別子です。transaction hashと同じ文字列になる実装があっても、常にtransaction hashだと決め付けず、opaqueなIDとして保存します。
拒否は送信前の証拠になる
EIP-5792では、atomic executionを要求したのにwalletが対応できない場合は5760、要求chainに対応しない場合は5710で拒否できます。どちらも、対象contractのrevertではありません。
| code | 境界 | 次に直す値 |
|---|---|---|
5700 |
必須capabilityが非対応 | requestのcapabilitiesとoptional指定 |
5710 |
chain IDが非対応 | selected chain、request chainId、capability map |
5760 |
atomicityが非対応 | atomicRequiredとwallet / accountの準備状態 |
4001 |
利用者がrequestを拒否 | UIで確認したrequestと再試行方針 |
experimental_fallbackで個別transactionへ切り替える場合、forceAtomic: trueかつ複数callなら同じ保証を保てません。atomicを必須にした処理を、黙ってsequential transactionへ変えないでください。
ERC-7821のexecuteでcall列を実行する
ERC-7821は、次の最小interfaceを定めます。
interface IERC7821 {
function execute(bytes32 mode, bytes calldata executionData) external payable;
function supportsExecutionMode(bytes32 mode) external view returns (bool);
}
この記事で使うsingle-batch / default execution / no-opData modeは、先頭byteだけが0x01のbytes32です。
bytes32 constant BATCH_MODE = bytes32(uint256(1) << 248);
// 0x0100000000000000000000000000000000000000000000000000000000000000
executionDataは、ERC-7579のExecution[]をABI encodeした値です。
struct Execution {
address target;
uint256 value;
bytes callData;
}
Execution[] memory calls = new Execution[](2);
calls[0] = Execution(address(target), 0, abi.encodeCall(target.add, (2)));
calls[1] = Execution(address(target), 0, abi.encodeCall(target.add, (3)));
account.execute(BATCH_MODE, abi.encode(calls));
OpenZeppelin Contracts 5.6.1のERC7821は、このsingle-batch / no-opData modeを実装します。既定ではmsg.sender == address(this)だけを許し、unsupported modeを拒否し、途中のcallが失敗するとreasonを外へ返します。
ownerやEntryPointから呼ぶaccountでは、認可hookを明示的にoverrideします。
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view override returns (bool) {
return caller == owner
|| caller == authorizedEntryPoint
|| super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
これは最小例です。production accountでは、owner rotation、multisig、session key、nonce、署名domain、upgrade、EntryPoint versionなどの認可条件が別に必要です。tokenのallowanceと一回限りの署名権限を分ける観点はapprove・permit・Permit2・temporary approvalの比較にも共通します。
EOA・EIP-7702・ERC-4337で到達経路が変わる
同じcallsでも、executorへ届くまでの経路は同じではありません。
| account形態 | walletからの代表的な経路 | ERC-7821へ届く条件 | 先に確認する証拠 |
|---|---|---|---|
| plain EOA | 個別transaction、またはwallet独自のbatch機構 | EOA自体にexecutor codeはない | atomic capability、transaction数、status receipts |
| EIP-7702 delegated EOA | EOA addressのcontextで委任先codeを実行 | 委任先がERC-7821を実装し、self / signer認可を満たす | authorization、delegation indicator、implementation、nonce |
| ERC-4337 smart account | bundlerがUserOperationをEntryPointへ入れる | accountがEntryPointを認可し、callDataからexecutorへ渡す | EntryPoint version、userOpHash、account validation、receipt |
ローカルcontractでは、ownerからのcall、認可済みEntryPoint addressからのcall、EIP-7702 delegated EOAのself-callを別々に通しました。plain EOAについては、codeがないため同じexecute entrypointを持つとは扱いません。
この比較は経路の説明です。EIP-7702なら必ずERC-7821、ERC-4337なら必ずEIP-5792という関係ではありません。
wallet_getCallsStatusで最終結果を確認する
送信がIDを返した時点では、処理は確定していない場合があります。wallet_getCallsStatusまたはwaitForCallsStatusで、同じIDの最終statusを取得します。
import { getCallsStatus, waitForCallsStatus } from '@wagmi/core'
const immediate = await getCallsStatus(config, { connector, id })
const settled = await waitForCallsStatus(config, {
connector,
id,
pollingInterval: 1_000,
timeout: 60_000,
})
console.log({
immediate: immediate.statusCode,
settled: settled.statusCode,
atomic: settled.atomic,
receipts: settled.receipts,
})
EIP-5792 v2のstatus codeは、100番台をpending、200番台をsuccess、400〜600番台をfailureとして分けます。
| status | 意味 | state / receiptの確認 |
|---|---|---|
100 |
pending | 同じIDで待つ。再送して別batchを作らない |
200 |
success | receipt、target state、期待eventを照合 |
400 |
offchain failure | wallet側reasonとrequestを保存 |
500 |
全体revert | atomic field、reverted receipt、state rollbackを確認 |
600 |
一部revert | atomic=falseの可能性と各receiptを個別確認 |
status=200でも、どのtransactionが含まれたか、receipt statusは何か、target stateが期待値かを確認します。通常transaction receiptを読む基本は失敗transactionの確認手順へ戻れます。
ローカル再現で4つのRPC結果と7つのcontract条件を確認する
Solidity 0.8.36、OpenZeppelin Contracts 5.6.1、Foundry / Anvil 1.8.0、viem 2.56.0、wagmi Core 3.4.0を固定しました。chain IDは31337です。
contract側では7条件を確認しました。
| 条件 | 結果 |
|---|---|
| single-batch mode | supportsExecutionModeがtrue |
| ownerが2-callを実行 | 2 + 3の順でstateが5 |
| 二つ目がcustom errorでrevert | 一つ目の+7も戻り、stateは5のまま |
| unsupported mode | UnsupportedExecutionModeでrevert |
| unauthorized caller | AccountUnauthorizedでrevert |
| authorized EntryPoint route | 同じexecutorへ到達して成功 |
| EIP-7702 self-call route | delegated EOA contextで成功 |
Anvil上のEIP-1193 wallet surfaceをwagmi actionsから呼んだ結果は次のとおりです。
{
"capabilities": {
"supported": "supported",
"unsupported": "unsupported"
},
"twoCallSuccess": {
"immediateStatus": 100,
"status": 200,
"atomic": true,
"receiptStatus": "success",
"finalValue": "5"
},
"secondCallRevert": {
"status": 500,
"atomic": true,
"receiptStatus": "reverted",
"valueAfterRollback": "5"
},
"rejectedBeforeSubmission": {
"unsupportedWallet": 5760,
"chainMismatch": 5710
}
}
送信直後は100、mining後は200でした。二つ目のcallを失敗させたbatchはreceiptがrevertedとなり、一つ目のstate changeも残りませんでした。
この結果から言えるのは、固定したローカル実装でwallet RPCとERC-7821 executorを接続できたことです。実在walletのUI、upgrade flow、chain対応、bundler、paymaster、session key、production認可の互換性までは証明しません。
EIP-7702 self-callを実運用へつなぐ前に、designation変更とaccount state移行を分けます。同じEOAへのdelegate設定・変更・解除を追う検証では、compatible / incompatible storage、authorization nonce、clear後のallowanceまで確認しています。
複数のbrowser walletが同時にinstallされた環境では、batch requestを作る前にprovider selectionを固定します。EIP-6963で複数walletを検出・選択する実装では、window.ethereumの上書き順へ依存せず、選んだ一つのproviderだけへrequestする境界を確認できます。
実walletで記録するchecklist
実環境へ持ち込むときは、成功画面だけでなく次を一組で残します。address、calldata、receiptを共有する際は、個人情報や秘密情報を除いてください。
- wallet名、Extension / Mobile、version、確認日時
- chain名と
chainId - account addressとEOA / EIP-7702 / ERC-4337の区分
wallet_getCapabilitiesのraw responsewallet_sendCallsのversion、from、ordered calls、atomicRequired- 返されたopaque ID
wallet_getCallsStatusのraw response、status、atomic- receiptごとのtransaction hashとstatus
- target stateと期待event
- EntryPointやexecutorを使った場合は、そのaddress、version、implementation
実在walletの対応情報を書く場合は、「2026年8月30日、製品version X、環境Y、chain Zで確認」のように範囲を限定します。別versionや別accountへ一般化しません。
実装時の判断順
最後に、実装と障害調査の順序をまとめます。
- wallet RPCとaccount executorを別の層として図にする
- 対象account・chainのcapabilityを取得する
version、chainId、from、ordered callsを固定する- atomicが必要なら
atomicRequiredを明示し、非対応時は送信前に止める - accountのmode、execution encoding、authorized executorを確認する
- EOA / EIP-7702 / ERC-4337のどの経路かを記録する
- IDを保存し、statusがterminalになるまで追う
- status、atomic field、receipt、target stateを照合する
一つの確認表示を一つのtransactionとみなさず、requestのatomic要件を実行結果とみなさないことが重要です。EIP-5792で入口と結果を追い、ERC-7821を使う場合はaccount側のmode・認可・rollbackを別に検証すれば、batch callの責任境界を崩さず実装できます。
広告・sponsorとの分離
本文のgas sponsorやpaymasterはprotocol上の技術概念です。3MIKANの広告・affiliate sponsorとは関係ありません。
この記事にwallet接続、transaction送信の誘導、provider推奨、affiliate CTAはありません。将来provider広告を追加する場合も、仕様status、capability、検証結果、failure matrixから明確に分離します。
確認した一次情報
- EIP-5792 Wallet Call API (Final)確認日: 2026/08/30
- ERC-7821 Minimal Batch Executor Interface (Draft)確認日: 2026/08/30
- EIP-7702 Set EOA account code確認日: 2026/08/30
- ERC-4337 Account Abstraction Using Alt Mempool確認日: 2026/08/30
- OpenZeppelin Contracts 5.6.1 release確認日: 2026/08/30
- OpenZeppelin ERC7821 5.6.1 source確認日: 2026/08/30
- viem getCapabilities確認日: 2026/08/30
- viem sendCalls確認日: 2026/08/30
- viem getCallsStatus確認日: 2026/08/30
- wagmi Core getCapabilities確認日: 2026/08/30
- wagmi Core sendCalls確認日: 2026/08/30
- viem 2.56.0 release確認日: 2026/08/30
- wagmi 3.4.0 release確認日: 2026/08/30
- Foundry v1.8.0確認日: 2026/08/30
- Solidity 0.8.36 documentation確認日: 2026/08/30



