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

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などの秘密情報を除き、次を一組として保存します。
chainId- 利用したEntryPoint addressとversion
sendernonce- 署名前後で使ったUserOperation fields
- 計算できた場合は
userOpHash - bundler / paymaster serviceのraw JSON-RPC error
- inclusion後ならbundle transaction hashとUserOperation receipt
eth_sendUserOperationがmempool受付前に失敗すると、bundle transaction hashはありません。ERC-7769では、シミュレーションとpool受付を通った場合にだけuserOpHashを返す形です。errorしかない段階でExplorerを探し続けず、まずraw RPC responseを読みます。
一方、userOpHashを受け取った後は、eth_getUserOperationByHashとeth_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のtype、domain、messageを分けて検算する方法は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 / FailedOpWithRevertのAA2x、inner bytes |
| paymaster | web service policy、deposit、contract validation、期限 | service error、ERC-7769 code、AA3x、paymaster address、inner bytes |
| execution | accountのcallDataからtargetを実行 |
UserOperationEvent.success、UserOperationRevertReason、inner revert |
| postOp | paymasterが実行結果とgas costを後処理 | PostOpRevertReason、UserOperationEvent.success=false |
bundlerのRPC errorはmempoolへ入る前の証拠
ERC-7769のeth_sendUserOperationは、成功時にuserOpHash、失敗時にcode、message、必要に応じて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の有効期間外 | validAfter、validUntil、現在時刻 |
-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の期間外 | validAfter、validUntil |
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の期間外 | validAfter、validUntil |
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のsuccessがfalseなら個別処理は失敗です。
ERC-7769のUserOperation receiptは、個別のsuccess、reason、logsに加え、bundle全体のtransaction receiptを含みます。次の順に読むと、別UserOperationのlogを混ぜにくくなります。
userOpHashが一致するかsender、nonce、paymasterが送信値と一致するか- 個別
successは何か reasonまたはreason eventがあるか- bundle
transactionHashとreceiptstatusは何か - 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.successはfalseでした。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で使う確認順
最後に、障害対応を次の順へ固定します。
chainId、EntryPoint address、version、bundler URLの識別子を固定する- 送信したunpacked UserOperationとraw RPC responseを保存する
- userOpHashがなければ、RPC codeからfield、シミュレーション、reputation、paymaster serviceへ分岐する
AA2xならaccount deploy、prefund、期間、signature、nonceを分けるAA3xならpaymaster service、deposit、stake、validation、期間を分ける- userOpHashがあればUserOperation receiptとbundle transaction receiptの両方を取る
UserOperationEventをuserOpHashで絞り、個別successを読む- reason eventのraw bytesを保存してから正しいABIでデコードする
- postOp failureではtarget stateが残ったと決めつけず、最終stateを読む
- 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の段階とは分けて確認できます。
確認した一次情報
- ERC-4337 Account Abstraction Using Alt Mempool確認日: 2026/08/30
- ERC-7769 JSON-RPC API for ERC-4337 (Draft)確認日: 2026/08/30
- ERC-7677 Paymaster Web Service Capability (Review)確認日: 2026/08/30
- eth-infinitism Account Abstraction v0.8.0 release確認日: 2026/08/30
- EntryPoint v0.8.0 source確認日: 2026/08/30
- IEntryPoint v0.8.0 events and errors確認日: 2026/08/30
- PackedUserOperation v0.8.0 source確認日: 2026/08/30
- @account-abstraction/contracts 0.8.0確認日: 2026/08/30
- viem Account Abstraction guide確認日: 2026/08/30
- viem 2.56.0 release確認日: 2026/08/30
- Solidity 0.8.36 documentation確認日: 2026/08/30
- Foundry v1.8.0確認日: 2026/08/30



