Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.startale.com/llms.txt

Use this file to discover all available pages before exploring further.

When a UserOperation fails it surfaces one of two ways: the bundler rejects it during validation (before it touches the chain), or the EntryPoint reverts it during execution (onchain). The error codes and tools below cover both paths.

AA error codes

The ERC-4337 spec reserves the AA prefix for EntryPoint errors. Every string in this table can appear in a UserOperationExecutionError thrown by sendUserOperation or debugUserOperation.
CodePhaseCauseFix
AA13Account creationinitCode ran out of gas or revertedIncrease verificationGasLimit; check the factory constructor
AA21Account validationAccount did not prefund the EntryPointAdd ETH to the account or attach a paymaster
AA23Account validationvalidateUserOp revertedDecode the revert reason (see below); usually a bad signature or nonce
AA25Account validationInvalid nonceUse account.getNonce() immediately before signing
AA31Paymaster validationPaymaster deposit too lowTop up the paymaster balance in the SCS Portal
AA33Paymaster validationvalidatePaymasterUserOp reverted or ran out of gasVerify paymasterId and increase paymasterVerificationGasLimit
AA34Paymaster validationPaymaster signature invalidConfirm the paymaster RPC URL and API key are correct
AA40GasverificationGasLimit exceededIncrease verificationGasLimit
AA41GasverificationGasLimit too low for executionLet the bundler estimate gas; do not override gas limits manually
AA42GascallGasLimit too lowIncrease callGasLimit or remove the manual override
AA51ExecutionpostOp revertedThe paymaster’s post-operation hook failed; check paymaster logs

debugUserOperation

StartaleAccountClient exposes debugUserOperation as a drop-in replacement for sendUserOperation. It prints the packed UserOperation, submits it to the bundler, and decodes any AA error code that comes back.
const hash = await smartAccountClient.debugUserOperation({
  calls: [{ to: targetAddress, data: callData, value: 0n }],
})
Console output on failure:
Packed userOp: [{ sender, nonce, callData, ... }]
Bundler userOp: { sender, nonce, ... }
{ aaError: { title: "...", metaMessages: ["..."] } }
UserOperationExecutionError: ...
Replace sendUserOperation with debugUserOperation during development; swap back before shipping.

Tenderly simulation

debugUserOperation generates a Tenderly simulation URL automatically when these three environment variables are set:
TENDERLY_API_KEY=...
TENDERLY_ACCOUNT_SLUG=...
TENDERLY_PROJECT_SLUG=...
When the variables are present, a URL is logged to the console before the bundler call:
{ tenderlyUrl: 'https://dashboard.tenderly.co/your-account/your-project/simulator/new?...' }
Open the URL in a browser to step through handleOps execution frame by frame, inspect storage reads and writes, and pinpoint the exact revert location, including inside the EntryPoint, your account contract, and your target contract.

Decoding a revert reason manually

If you catch a UserOperationExecutionError, the revert reason is nested inside cause.cause.data:
import { parseErrorMessage } from "@startale-scs/aa-sdk"

try {
  await smartAccountClient.sendUserOperation({ calls: [...] })
} catch (error) {
  const message = parseErrorMessage(error)
  console.error(message)
}
parseErrorMessage extracts the human-readable string from the failedOp or FailedOpWithRevert revert ABI, the reason JSON field bundlers sometimes attach, and raw hex calldata, whichever format the bundler returns.

Tracing onchain with Blockscout

If the UserOperation lands onchain but the inner call reverts, find it in Blockscout:
  1. Take the UserOperation hash returned by waitForUserOperationReceipt.
  2. Open soneium.blockscout.com (mainnet) or soneium-minato.blockscout.com (testnet).
  3. Paste the hash into the search bar.
  4. Open the User Ops tab and click Decode input data to see the decoded callData and the inner call trace.
The UserOperationEvent log on the same transaction also tells you whether execution succeeded (success: true) or was reverted by the target (success: false with nonzero actualGasCost).

Common patterns

Signature failure (AA23) during development The most frequent cause is a stale nonce or a signer that returns a signature before the UserOp fields are fully populated. Always call account.getNonce() immediately before signing and let the SDK’s sendUserOperation populate gas estimates before the signature step. Gas underestimation Do not hard-code gas limits. The bundler’s eth_estimateUserOperationGas call accounts for the account’s validation logic, the paymaster, and the target call. Overriding callGasLimit or verificationGasLimit below the estimate causes AA41 or AA42 rejections. Paymaster deposit (AA31) The SCS Paymaster dashboard shows your current deposit. If a spike in user activity drains the balance, new UserOperations are rejected at the bundler level before touching the chain. Set up a low-balance alert in the portal.

Next steps

Parallel transactions

Send independent UserOperations on separate nonce lanes to avoid serialization.

Contract interactions

Encode calldata and handle errors in standard single and batched calls.

Sponsored transactions

Configure a paymaster so users never see a gas prompt.

Supported networks

Chain IDs, bundler URLs, and paymaster URLs per network.