3MIKAN
仮想通貨直コン

ERC-4337 UserOperation失敗解析|bundler・paymaster・execution

ERC-4337 UserOperationの失敗をbundler、account validation、paymaster、execution、postOpへ分け、AAxx・receipt・inner revertを調べます。

3MIKANのブランドキャラクターが一つのUserOperationを複数の検査地点と証拠の軌跡から調べる記事画像

ERC-4337のUserOperationは、通常のtransaction hashだけでは失敗を追えません。bundlerのmempoolへ入る前に拒否される場合と、bundleへ入った後にaccountの処理がrevertする場合では、残る証拠が違うためです。

調査では、送信時のerror、validationのAAxx、UserOperationEvent、inner revertを同じuserOpHashへ対応させます。最初から「署名エラー」「paymaster障害」と決めず、どの段階まで進んだかを先に固定します。

この記事はERC-4337 Final仕様と、@account-abstraction/contracts 0.8.0のEntryPointを基準にします。ERC-7769のRPC仕様は2026年8月30日時点でDraft、ERC-7677のpaymaster web service仕様はReviewです。利用中のbundler、EntryPoint version、account実装によって返り値が異なる可能性があります。

外部bundler、public RPC、実在wallet、deployed smart account、実資産は使いません。最後のlocal再現も、特定providerのmempoolやreputation rulesへの互換性を証明するものではありません。

まず保存する8つの証拠

送信失敗の画面だけを残しても、後から同じ分岐をたどれません。application logでは、認証headerやRPC keyなどの秘密情報を除き、次を一組として保存します。

  1. chainId
  2. 利用したEntryPoint addressとversion
  3. sender
  4. nonce
  5. 署名前後で使ったUserOperation fields
  6. 計算できた場合はuserOpHash
  7. bundler / paymaster serviceのraw JSON-RPC error
  8. inclusion後ならbundle transaction hashとUserOperation receipt

eth_sendUserOperationがmempool受付前に失敗すると、bundle transaction hashはありません。ERC-7769では、シミュレーションとpool受付を通った場合にだけuserOpHashを返す形です。errorしかない段階でExplorerを探し続けず、まずraw RPC responseを読みます。

一方、userOpHashを受け取った後は、eth_getUserOperationByHasheth_getUserOperationReceiptを調べます。通常transactionのreceipt、calldata、logsを読む基本順はviemで失敗トランザクションを解析するでも確認できます。

UserOperationのfieldsとhashを固定する

offchainのUserOperationは、次の役割に分けると調べやすくなります。

field 役割 最初に照合する値
sender 実行主体となるsmart account address、codeの有無、account implementation
nonce replay防止と並列lane EntryPointのgetNonce(sender, key)
factory / factoryData 未deploy accountの作成 factory address、予測sender、init result
callData accountへ渡す実行内容 account ABI、selector、inner target / value / data
callGasLimit account main executionのgas上限 シミュレーション結果と実際の使用量
verificationGasLimit account / factory validationの上限 validation revertとgas超過
preVerificationGas calldataやbundle overheadを含むbundler向け費用 bundler estimate
fee fields UserOperationが支払えるgas単価 chain fee、max fee、priority fee
paymaster fields gas sponsor contractと検証data address、deposit、期限、policy、gas limits
signature accountが定義する認可data owner、digest、format、現在のaccount state

EntryPointへ渡すときはPackedUserOperationになり、factory fieldsはinitCode、gas上限はaccountGasLimits、fee fieldsはgasFees、paymaster fieldsはpaymasterAndDataへまとめられます。RPCで見たfield名とonchain calldataを比べるときは、unpackedとpackedを混同しないでください。

userOpHashはsignature以外の内容と実行環境を結ぶ

ERC-4337では、userOpHashはsignatureを除くUserOperation、EntryPoint、chainIdへ結び付けられます。v0.8.0はEIP-712互換のUserOperation hashを導入しました。

同じcallDataでも、EntryPointまたはchainIdが変われば別の認可対象です。EIP-712のtypedomainmessageを分けて検算する方法はEIP-712署名の検証手順へつなげられます。

accountのsignature形式はERC-4337が一律に決めません。EOA ownerのECDSAだけでなく、contract owner、multisig、session keyなどを実装できます。contract signatureと現在stateの関係はERC-1271の署名検証を参照してください。

Passkey assertionをUserOperation.signatureへ入れる場合は、challengeへuserOpHashをbindしたうえで、origin、RP ID hash、flags、P-256公開鍵をaccount policyへ対応させます。Passkey smart accountのWebAuthn・P-256検証では、EIP-7951 native pathとSolidity fallbackも同じvectorで比較しています。

nonceは192-bit keyと64-bit sequence

EntryPointのnonceは、上位192-bitのkeyと下位64-bitのsequenceとして扱われます。keyごとにsequenceが進むため、単純な「前回値 + 1」だけでは別laneを説明できません。

const nonce = await publicClient.readContract({
  address: entryPoint,
  abi: entryPointAbi,
  functionName: 'getNonce',
  args: [sender, nonceKey],
})

bundlerのpending poolに同じsender・keyのUserOperationがあると、chain上の値だけでは次のsequenceを判断できない場合があります。通常transactionのpending・replacementとnonceを分ける考え方はnonceと置換transactionの確認方法にも共通します。

失敗を5つの段階へ分ける

一つのUserOperationは、すべての段階で同じ種類のerrorを返すわけではありません。

段階 主な処理 失敗時に残りやすい証拠
bundler受付 field形式、supported EntryPoint、シミュレーション、mempool / reputation rules JSON-RPC error code、message、data。tx hashなし
account validation deploy、signature、validity range、prefund、nonce FailedOp / FailedOpWithRevertAA2x、inner bytes
paymaster web service policy、deposit、contract validation、期限 service error、ERC-7769 code、AA3x、paymaster address、inner bytes
execution accountのcallDataからtargetを実行 UserOperationEvent.successUserOperationRevertReason、inner revert
postOp paymasterが実行結果とgas costを後処理 PostOpRevertReasonUserOperationEvent.success=false
UserOperationがbundler受付、account検証、paymaster、execution、postOpを進み各段階で残す証拠
tx hashがないRPC拒否と、inclusion後にeventが残るexecution失敗を最初に分けます。

bundlerのRPC errorはmempoolへ入る前の証拠

ERC-7769のeth_sendUserOperationは、成功時にuserOpHash、失敗時にcodemessage、必要に応じてdataを返します。2026年8月30日時点でDraftなので、実装ごとの差を前提にraw responseを保存してください。

code 仕様上の意味 次に見る場所
-32602 UserOperationのfield・形式が不正 hex形式、空bytesの0x、factory / paymaster fieldsの組
-32500 wallet作成またはEntryPoint validationで拒否 message内のAAxx、EntryPoint version
-32501 paymaster contract validationで拒否 data.paymaster、paymaster inner reason
-32502 ERC-7562 validation rule違反 opcode / storage access、bundler rule set
-32503 accountまたはpaymasterの有効期間外 validAftervalidUntil、現在時刻
-32504 paymasterがreputation rulesでthrottled / banned paymaster address、bundler policy
-32505 paymasterのstake / unstake delay不足 minimum値と現在stake
-32507 wallet signature check失敗 signer、userOpHash、signature format
-32508 paymaster残高がpending UserOperationsを覆えない paymaster balance、pool内の予約分

たとえば次のresponseは、onchain executionのrevertではありません。bundlerがsignature validationで受付を止めた段階です。

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32507,
    "message": "AA24 signature error"
  }
}

eth_estimateUserOperationGasでも、accountのinner callやpaymasterのpostOpまで含むerrorが返る場合があります。estimateのrevert dataを読む基本はestimateGas失敗の切り分け、custom errorのABIデコードはcustom errorの読み方へ分けて確認できます。

account validationはAA2xとinner bytesを分ける

EntryPoint v0.8.0の代表的なaccount側errorは次のとおりです。AAxxは原因の入口であり、特にAA23ではinner bytesが本体です。

error v0.8.0で示す境界 確認項目
AA20 account not deployed sender codeがなく作成にも進めない sender、factory / initCode、予測address
AA21 didn't pay prefund account depositと送金で必要額を満たさない EntryPoint deposit、account ETH、gas fields
AA22 expired or not due account validationDataの期間外 validAftervalidUntil
AA23 reverted validateUserOpがrevert FailedOpWithRevert.innerをaccount ABIでデコード
AA24 signature error accountがsignature失敗を返す signer、digest、signature format、account state
AA25 invalid account nonce EntryPointのkey / sequence不一致 getNonce、pending pool、利用key
AA26 over verificationGasLimit validationが上限を超える estimate、factory / account validation cost

signature mismatchは、仕様上validateUserOpからSIG_VALIDATION_FAILEDを返す経路です。account code自体がrevertしたAA23とは分けます。

AA23のinner bytesが0xでなければ、先頭4-byteをaccount ABIのerror selectorと照合します。ABI、selector、event dataを手で検算する場合はcalldataとevent logsの読み方を使えます。

paymasterはweb service・validation・postOpを混ぜない

「paymaster error」には少なくとも三つの場所があります。

1. paymaster web serviceがsponsorを断る

ERC-7677は、gas estimate用のstub dataを返すpm_getPaymasterStubDataと、最終dataを返すpm_getPaymasterDataを定義します。これは2026年8月30日時点でReviewです。

policy ID、対象call、quota、user eligibilityなどをserviceが確認し、bundler送信前に拒否できます。この段階ではEntryPointのAA3xもbundle tx hashもありません。HTTP status、JSON-RPC error、service contextを保存します。

2. EntryPointがdepositとvalidationを確認する

paymaster fieldsがあると、EntryPointはaccountへ要求するmissingAccountFundsを0にし、代わりにpaymaster depositとvalidatePaymasterUserOpを確認します。

error v0.8.0で示す境界 確認項目
AA30 paymaster not deployed paymaster codeがない address、chain、deployment
AA31 paymaster deposit too low EntryPoint depositがprefund未満 depositとstakeを分けて読む
AA32 paymaster expired or not due paymaster validationDataの期間外 validAftervalidUntil
AA33 reverted paymaster validationがrevert FailedOpWithRevert.inner、policy / signature
AA34 signature error paymaster側signature check失敗 paymaster digestと署名対象fields
AA36 over paymasterVerificationGasLimit paymaster validation gas超過 estimateと指定上限

depositは将来のUserOperation gasを払う残高、stakeはreputation上の制約に使うlockです。AA31でstakeだけを増やしても、deposit不足は解消しません。

3. postOpが失敗する

paymaster validationが空でないcontextを返すと、main execution後にpostOpが呼ばれます。v0.8.0ではpostOpのrevertをPostOpRevertReasonへ残し、個別UserOperationを失敗として扱う経路があります。

main targetのcallが一度成功して見えても、同じ内側の処理でpostOpがrevertすれば、その実行stateは取り消され得ます。target eventだけを単独で見ず、最後のUserOperationEventとstateを確認してください。

executionではbundle receiptと個別結果を分ける

bundlerは複数UserOperationsを一つのhandleOps transactionへまとめられます。そのため、bundle transactionのreceipt status=successは、すべてのinner call成功を意味しません。

v0.8.0のUserOperationEventには次が入ります。

event UserOperationEvent(
  bytes32 indexed userOpHash,
  address indexed sender,
  address indexed paymaster,
  uint256 nonce,
  bool success,
  uint256 actualGasCost,
  uint256 actualGasUsed
);

accountのexecutionがreason付きでrevertすると、UserOperationRevertReasonへraw bytesが残ります。bundle自体が正常に確定していても、対象eventのsuccessfalseなら個別処理は失敗です。

bundle transaction receiptの内側に複数のUserOperation結果と個別のeventがある関係
transaction receiptはbundle全体、UserOperation receiptのsuccess・reason・logsは一つのuserOpHashに対応します。

ERC-7769のUserOperation receiptは、個別のsuccessreasonlogsに加え、bundle全体のtransaction receiptを含みます。次の順に読むと、別UserOperationのlogを混ぜにくくなります。

  1. userOpHashが一致するか
  2. sendernoncepaymasterが送信値と一致するか
  3. 個別successは何か
  4. reasonまたはreason eventがあるか
  5. bundle transactionHashとreceipt statusは何か
  6. target stateと期待eventが残ったか

viemでreceiptとinner revertを読み解く

viem 2.56.0ではbundler clientからUserOperation receiptを取得できます。provider固有fieldへ依存する前に、標準fieldを先に取り出します。

const userOpReceipt = await bundlerClient.getUserOperationReceipt({
  hash: userOpHash,
})

if (!userOpReceipt) {
  // pending、未認識、dropの候補。送信時responseとmempool状態へ戻る
  return
}

console.log({
  userOpHash: userOpReceipt.userOpHash,
  success: userOpReceipt.success,
  reason: userOpReceipt.reason,
  transactionHash: userOpReceipt.receipt.transactionHash,
  transactionStatus: userOpReceipt.receipt.status,
})

reason eventのbytesは、失敗したtargetまたはaccountのABIでデコードします。

import { decodeErrorResult, parseAbi, parseEventLogs } from 'viem'

const entryPointEvents = parseAbi([
  'event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason)',
])

const [reasonLog] = parseEventLogs({
  abi: entryPointEvents,
  eventName: 'UserOperationRevertReason',
  logs: userOpReceipt.logs,
  strict: true,
})

if (reasonLog) {
  const decoded = decodeErrorResult({
    abi: targetAbi,
    data: reasonLog.args.revertReason,
  })
  console.log(decoded.errorName, decoded.args)
}

ABIがproxyのimplementationと一致していない、reason bytesが空、または途中でwrapper errorが入る場合はデコードできません。元bytesを消さず、proxy implementation、account wrapper、targetの順にABI候補を照合します。

local再現で10ケースを比較する

@account-abstraction/[email protected]の公式EntryPointをlocal chainへdeployし、Solidity 0.8.36、Foundry / Anvil 1.8.0、viem 2.56.0で同じUserOperationを段階的に変えました。

入力 段階 観測結果
正しい署名・nonce・prefund execution bundle receipt success、UserOperationEvent.success=true
別keyの署名 account validation AA24 signature error
account policyがrevert account validation AA23 reverted + inner AccountPolicyDenied()
sequenceを1つ先へ変更 nonce AA25 invalid account nonce
account残高とdepositを0 prefund AA21 didn't pay prefund
paymaster depositを0 paymaster prefund AA31 paymaster deposit too low
paymaster policyがrevert paymaster validation AA33 reverted + inner PaymasterPolicyDenied()
paymaster期限を過去へ設定 paymaster validity AA32 paymaster expired or not due
targetがcustom errorでrevert execution bundle receipt success、個別success false、TargetFailure(0x4337)
postOpがcustom errorでrevert postOp PostOpRevertReason、個別success false

execution失敗のAnvil receiptでは、bundle transactionはsuccess、同じreceipt内のUserOperationEvent.successfalseでした。UserOperationRevertReasonのbytesをtarget ABIでデコードすると、TargetFailureとmarker 0x4337へ戻せます。

bad signatureはinclusion前のシミュレーションでFailedOp(0, "AA24 signature error")になり、ERC-7769上ではwallet signature failureの-32507へ対応する境界です。これは外部bundlerを動かした結果ではなく、RPC分類と公式EntryPoint errorを対応させたlocal確認です。

productionで使う確認順

最後に、障害対応を次の順へ固定します。

  1. chainId、EntryPoint address、version、bundler URLの識別子を固定する
  2. 送信したunpacked UserOperationとraw RPC responseを保存する
  3. userOpHashがなければ、RPC codeからfield、シミュレーション、reputation、paymaster serviceへ分岐する
  4. AA2xならaccount deploy、prefund、期間、signature、nonceを分ける
  5. AA3xならpaymaster service、deposit、stake、validation、期間を分ける
  6. userOpHashがあればUserOperation receiptとbundle transaction receiptの両方を取る
  7. UserOperationEventをuserOpHashで絞り、個別successを読む
  8. reason eventのraw bytesを保存してから正しいABIでデコードする
  9. postOp failureではtarget stateが残ったと決めつけず、最終stateを読む
  10. provider固有errorは標準codeと分けて記録し、version変更時に再確認する

tx hashがない段階、validationでbundle全体がrevertする段階、bundleは確定して個別executionだけ失敗する段階では、担当も証拠も違います。AAxxだけを検索するより、どのRPC・contract・eventがその値を返したかまで対応させると、次の調査先を早く絞れます。

EIP-7702でEOAにaccount logicを持たせる場合、authorizationが使うaccount nonceと、delegateのapplication nonceやERC-4337 nonce keyは別です。delegationを設定・変更・解除するlifecycle検証で、self executorのnonce増加とclear後に残るstateを先に分けられます。

UserOperationとは別のoff-chain messageを未デプロイaccount addressで検証する場合は、ERC-6492のcounterfactual signature検証へ進めます。UserOperationのfactory / factoryDataと、ERC-6492 wrapperのfactory callは用途が違うため、同じfactoryを使っても成功証拠を共有しません。

一つのwallet requestへ複数callをまとめる入口から調べる場合は、wallet_sendCallsとERC-7821のbatch call検証へ進んでください。EIP-5792のstatus 100 / 200 / 500、atomic非対応の5760、account executorのrollbackをUserOperationの段階とは分けて確認できます。

確認した一次情報