3MIKAN
仮想通貨直コン

wallet_sendCallsとERC-7821|batch call実装と検証

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

3MIKANのブランドキャラクターが複数のcallを一つのケースへまとめ、walletとaccountの二つの検査地点へ通す記事画像

複数のcontract callを一度にwalletへ渡しても、一つの確認表示、一つのtransaction、atomic execution、gas sponsorが同時に保証されるわけではありません。EIP-5792はwalletへ複数callを依頼して結果を追うRPCを定め、ERC-7821はaccountが複数callを実行する最小interfaceを定めます。

この記事では、wallet側のwallet_getCapabilitieswallet_sendCallswallet_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 modeexecutionDataを検証し、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実装が決めます。

wallet RPCが複数callを受け取り、実行経路を選び、account executorへ渡してstatusを返す流れ
EIP-5792はrequestとstatusの境界、ERC-7821はaccount内のexecution境界です。

EIP-7702でEOAのaddressとstorageを保ちながらcodeを委任する仕組みはEIP-7702のdelegation確認手順、ERC-4337のEntryPointとUserOperationの失敗境界はUserOperationの失敗解析で詳しく分けています。

atomicは四つの意味を混ぜない

atomicという語を見たら、次の四つを別々に確認します。

  1. walletは要求したchainとaccountでatomic batchを扱えるか
  2. requestはatomic executionを必須にしたか
  3. 実行されたcall列は、一つのcallが失敗したとき全体を戻すか
  4. statusは実際の結果をatomicとして報告したか

wallet_sendCallsatomicRequired: 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.0sendCallsは、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だけが0x01bytes32です。

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.1ERC7821は、この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を個別確認
二つのcallが両方成功する場合、全体が戻る場合、一部だけ戻る場合の違い
request時の要件だけで終わらせず、最終status、atomic field、receipt、stateを同じIDへ結び付けます。

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を共有する際は、個人情報や秘密情報を除いてください。

  1. wallet名、Extension / Mobile、version、確認日時
  2. chain名とchainId
  3. account addressとEOA / EIP-7702 / ERC-4337の区分
  4. wallet_getCapabilitiesのraw response
  5. wallet_sendCallsversionfrom、ordered calls、atomicRequired
  6. 返されたopaque ID
  7. wallet_getCallsStatusのraw response、statusatomic
  8. receiptごとのtransaction hashとstatus
  9. target stateと期待event
  10. EntryPointやexecutorを使った場合は、そのaddress、version、implementation

実在walletの対応情報を書く場合は、「2026年8月30日、製品version X、環境Y、chain Zで確認」のように範囲を限定します。別versionや別accountへ一般化しません。

実装時の判断順

最後に、実装と障害調査の順序をまとめます。

  1. wallet RPCとaccount executorを別の層として図にする
  2. 対象account・chainのcapabilityを取得する
  3. versionchainIdfrom、ordered callsを固定する
  4. atomicが必要ならatomicRequiredを明示し、非対応時は送信前に止める
  5. accountのmode、execution encoding、authorized executorを確認する
  6. EOA / EIP-7702 / ERC-4337のどの経路かを記録する
  7. IDを保存し、statusがterminalになるまで追う
  8. 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から明確に分離します。

確認した一次情報