> ## 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.

# Debugging UserOperations

> Decode AA error codes, simulate failures with Tenderly, and trace UserOperations on Blockscout.

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.

<Note>
  Source: [`StartaleGroup/scs-aa-sdk`](https://github.com/StartaleGroup/scs-aa-sdk).
</Note>

## 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`.

| Code   | Phase                | Cause                                                | Fix                                                                    |
| ------ | -------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------- |
| `AA13` | Account creation     | `initCode` ran out of gas or reverted                | Increase `verificationGasLimit`; check the factory constructor         |
| `AA21` | Account validation   | Account did not prefund the EntryPoint               | Add ETH to the account or attach a paymaster                           |
| `AA23` | Account validation   | `validateUserOp` reverted                            | Decode the revert reason (see below); usually a bad signature or nonce |
| `AA25` | Account validation   | Invalid nonce                                        | Use `account.getNonce()` immediately before signing                    |
| `AA31` | Paymaster validation | Paymaster deposit too low                            | Top up the paymaster balance in the SCS Portal                         |
| `AA33` | Paymaster validation | `validatePaymasterUserOp` reverted or ran out of gas | Verify `paymasterId` and increase `paymasterVerificationGasLimit`      |
| `AA34` | Paymaster validation | Paymaster signature invalid                          | Confirm the paymaster RPC URL and API key are correct                  |
| `AA40` | Gas                  | `verificationGasLimit` exceeded                      | Increase `verificationGasLimit`                                        |
| `AA41` | Gas                  | `verificationGasLimit` too low for execution         | Let the bundler estimate gas; do not override gas limits manually      |
| `AA42` | Gas                  | `callGasLimit` too low                               | Increase `callGasLimit` or remove the manual override                  |
| `AA51` | Execution            | `postOp` reverted                                    | The 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.

```ts theme={null}
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:

```bash theme={null}
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`:

```ts theme={null}
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](https://soneium.blockscout.com) (mainnet) or [soneium-minato.blockscout.com](https://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

<CardGroup cols={2}>
  <Card title="Parallel transactions" icon="arrows-split-up-and-left" href="/aa-sdk/advanced/parallel-tx">
    Send independent UserOperations on separate nonce lanes to avoid serialization.
  </Card>

  <Card title="Contract interactions" icon="layer-group" href="/aa-sdk/tutorials/contract-interactions">
    Encode calldata and handle errors in standard single and batched calls.
  </Card>

  <Card title="Sponsored transactions" icon="gas-pump" href="/aa-sdk/tutorials/sponsored-tx">
    Configure a paymaster so users never see a gas prompt.
  </Card>

  <Card title="Supported networks" icon="globe" href="/aa-sdk/resources/supported-networks">
    Chain IDs, bundler URLs, and paymaster URLs per network.
  </Card>
</CardGroup>
