> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Common errors and debugging techniques for Crossmint wallet integrations

Sometimes transactions fail. This guide outlines common failure reasons and provides tools to help you identify the underlying causes.

<Note>
  Reverted transactions are marked as "failed" and will not be executed. There is no auto-retry mechanism for failed
  transactions; you must manually resubmit them if needed.
</Note>

## Common Error Types

Transaction errors typically fall into two categories: validation failures and execution failures.

### Validation Errors

Validation errors occur when the provided transaction data is invalid. These errors commonly relate to invalid data (e.g., `signature`) during transaction approval. Common issues include:

* Malformed signatures
* Invalid signatures (e.g., from an unauthorized wallet)
* Insufficient signer permissions for transaction approval

An example of a validation error:

```json theme={null}
"error": {
  "reason": "execution_reverted",
  "message": "Execution reverted, see 'revert' for details",
  "revert": {
    "type": "wallet_authorization",
    "reason": "Invalid signature",
    "reasonData": "AA24"
  }
}
```

Troubleshooting steps:

* Verify the transaction signer is valid
* Confirm you are signing the correct `message` data
* Verify the signing private key matches the `signer` wallet

### Execution Errors

Execution errors occur when a transaction is reverted during runtime. These typically relate to the `calls` provided during transaction creation. Common causes include:

* Another transaction with the same call was executed already
* State changes between transaction creation and execution (e.g. asset price updated exceeding slippage allowance)

The Crossmint API typically provides specific revert reasons:

```json theme={null}
"error": {
  "reason": "execution_reverted",
  "message": "Execution reverted, see 'revert' for details",
  "revert": {
    "type": "contract_call",
    "reason": "ERC20: transfer amount exceeds balance"
  }
}
```

On EVM smart wallets, `revert` can also include a `debug` object that classifies the failure and points to the call that reverted. See [Revert Debug Object](#revert-debug-object).

Troubleshooting:

* Check `revert.debug.details.code` when present, and follow the matching action in [Failure Codes](#failure-codes)
* Check for similar previous transactions
* Attempt to resubmit with the same parameters

### Wallet Errors

#### WalletNotAvailableError

Thrown by `getWallet()` when no wallet exists for the given parameters. This is expected in the get-then-create pattern where you first attempt to retrieve a wallet and fall back to creating one.

```typescript theme={null}
import { useWallet } from "@crossmint/client-sdk-react-ui";
import { WalletNotAvailableError } from "@crossmint/wallets-sdk";

const { getWallet, createWallet } = useWallet();

try {
    const wallet = await getWallet({ chain: "base-sepolia" });
} catch (error) {
    if (error instanceof WalletNotAvailableError) {
        // No wallet exists yet — create one
        const wallet = await createWallet({
            chain: "base-sepolia",
            recoveryMethods: [{ type: "email", email: "user@example.com" }],
        });
    }
}
```

<Note>
  In previous SDK versions, `getOrCreateWallet()` handled this automatically. In V1, wallet retrieval and creation are separate operations. See the [migration guide](/wallets/guides/migrate-to-v1) for details.
</Note>

## Common Pitfalls

These are frequent sources of confusion when working with the V1 SDK.

### Read-only wallet from `getWallet()`

`getWallet()` returns a wallet with no active signer unless a device signer is found locally. Calling `send()`, `approve()`, or `addSigner()` on a read-only wallet throws an error.

**Fix:** Call `wallet.useSigner()` to activate a signer before performing signing operations, or rely on automatic device signer initialization on the client side.

```typescript theme={null}
const wallet = await getWallet({ chain: "base-sepolia" });

// wallet.signer may be undefined — check before signing
if (wallet.signer == null) {
    await wallet.useSigner({ type: "passkey" });
}

await wallet.send("0x...", "usdc", "1.0");
```

### `useSigner()` does not accept locator strings

In V1, `useSigner()` only accepts signer config objects. Passing a locator string (e.g., `"evm-keypair:0x..."`) results in a type error.

```typescript theme={null}
// ❌ Old — no longer works
await wallet.useSigner("evm-keypair:0xABC...");

// ✅ New — pass a config object
await wallet.useSigner({ type: "external-wallet", address: "0xABC..." });
```

### `getWallet()` parameter mismatch between client and server

`getWallet()` has different signatures on client and server. Using the wrong one throws an error.

* **Client-side:** `getWallet({ chain: "base-sepolia" })` — pass wallet args directly. Passing a `walletLocator` string as the first argument throws.
* **Server-side:** `getWallet("0xWalletAddress", { chain: "base-sepolia" })` — requires a `walletLocator` string as the first parameter. Omitting it throws: `"getWallet on server side requires a walletLocator parameter."`

### Client-side input validation errors

The V1 SDK validates inputs before making API calls, failing fast with descriptive errors:

| Error | Thrown by | Cause |
| - | - | - |
| `InvalidAddressError` | `wallet.send()` | Invalid `to` address format for the chain (EVM: `0x` + 40 hex chars, Solana: base58, Stellar: `G` + 55 alphanumeric) |
| `InvalidChainError` | `createWallet()`, `getWallet()` | Unrecognized chain name |
| `InvalidAmountError` | `wallet.send()` | Zero, negative, `Infinity`, or `NaN` amount |

### Other Errors

If you encounter transaction failures without error details or receive no error message, there may be an issue with the Crossmint infrastructure. First, check <a href="https://status.crossmint.com" target="_blank">the status page</a> to verify API availability and try again later. If the problem continues, contact Crossmint support.

## Signer Is Not Active After Adding

When you add a signer on EVM, the operation is either a transaction or a signature, depending on `deployImmediately`:

* TypeScript SDK (`@crossmint/wallets-sdk`, and React / React Native): `addSigner()` defaults to `deployImmediately: true`, which installs the signer onchain as soon as the registration transaction confirms.
* REST API: `POST /2025-06-09/wallets/{walletLocator}/signers` defaults to `deployImmediately: false` for EVM, so the signer is installed lazily on its first transaction after the signature is approved.
* Flutter, Kotlin, and Swift SDKs: These SDKs do not currently expose `deployImmediately`, so they use the REST API default (deferred signature flow on EVM).

If a signer is stuck in `pending` or `awaiting-approval`, check whether the response contains a `transactionId` or a `signatureId` and approve the correct object:

* `transactionId` → call [approve transaction](/api-reference/wallets/approve-transaction)
* `signatureId` → call [approve signature](/api-reference/wallets/approve-signature)

See [Installation Timing](/wallets/guides/signers/add-signers#installation-timing) for details.

## Debugging

The Crossmint API simulates all transactions before submitting them onchain. Failed simulations prevent onchain execution.

In most cases, the transaction fails during the simulation. If that is the case, the `revert.reason` and `revert.reasonData` fields describe the failure, and on EVM smart wallets `revert.debug` adds structured details. Otherwise, you can use a block explorer to inspect the transaction.

### Revert Debug Object

`revert.debug` is an optional object on the `revert` of failed EVM smart wallet transactions. It appears in transaction responses and in transaction webhook payloads. `revert.reason` and `revert.reasonData` keep their existing format, so code that reads them continues to work.

`revert.debug` is only available for EVM smart wallets and is absent when the chain returned no information about the failure, so always fall back to `revert.reason`. The classification is best effort: `details.code` is `unknown` when the cause cannot be determined, and `call` is omitted when the failing call cannot be identified.

`revert.debug` contains:

* **`message`**: a one-sentence summary of the failure
* **`retryable`**: `true` when resubmitting the same transaction can succeed, either after you fix the cause (for example funds or gas) or without changes (for example a nonce conflict, a gas sponsorship failure, or a failure with no revert data). `false` when a contract call itself reverts and the calls must change.
* **`chain`** and **`blockNumber`**: the chain and the block whose state the failure was observed against
* **`revert`**: the raw revert returned by the chain — `selector`, `name`, `signature`, and positional `args`. `selector` is absent when the revert carried no data; `name` and `signature` are absent when the error could not be decoded.
* **`call`**: the call in the batch that reverted — `index`, `to`, `value`, `function`, and `args`. Absent when the failure cannot be attributed to a call, for example during wallet validation.
* **`details`**: the classified root cause. Branch on `details.code`; the other fields depend on the code.

Each entry in `args` has a Solidity `type` and a `value`, with integers as decimal strings. `args[].name` is present only when the ABI was supplied in the transaction request.

To reproduce the failure, send an `eth_call` from the EntryPoint to the wallet with `onChain.userOperation.callData` at `blockNumber`. Hook-enabled operations prefix the calldata with the `executeUserOp` selector, which must be stripped, and wallets not yet deployed need a state override (proxy code plus the EIP-1967 implementation slot).

This example shows a USDC transfer that exceeds the wallet balance:

```json theme={null}
"error": {
  "reason": "execution_reverted",
  "message": "Execution reverted, see 'revert' for details",
  "revert": {
    "type": "contract_call",
    "reason": "ERC20InsufficientBalance(0x1234567890abcdef1234567890abcdef1234abcd,500000,1000000)",
    "reasonData": "0xe450d38c0000…",
    "debug": {
      "message": "Wallet holds 0.5 USDC but the transfer requires 1 USDC",
      "retryable": true,
      "chain": "base",
      "blockNumber": "38104211",
      "revert": {
        "selector": "0xe450d38c",
        "name": "ERC20InsufficientBalance",
        "signature": "ERC20InsufficientBalance(address,uint256,uint256)",
        "args": [
          { "type": "address", "value": "0x1234567890abcdef1234567890abcdef1234abcd" },
          { "type": "uint256", "value": "500000" },
          { "type": "uint256", "value": "1000000" }
        ]
      },
      "call": {
        "index": 1,
        "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "value": "0",
        "function": "transfer(address,uint256)",
        "args": [
          { "type": "address", "name": "to", "value": "0x9999999999999999999999999999999999999999" },
          { "type": "uint256", "name": "amount", "value": "1000000" }
        ]
      },
      "details": {
        "code": "insufficient_erc20_balance",
        "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "symbol": "USDC",
        "required": "1000000",
        "available": "500000"
      }
    }
  }
}
```

### Failure Codes

| `details.code` | Extra fields | What to do |
| - | - | - |
| `insufficient_erc20_balance` | `token`, `symbol`, `required`, `available` | Fund the wallet with at least `required` of `token`, or lower the amount, then resubmit |
| `insufficient_erc20_allowance` | `token`, `symbol`, `spender`, `required`, `available` | Approve `spender` for at least `required` of `token`, then resubmit |
| `insufficient_native_balance` | `required`, `available` | Fund the wallet with enough native token, in wei, to cover the value sent plus gas |
| `out_of_gas` | `gasLimit`, `gasUsed` | Resubmit with a higher gas limit, or reduce the work done in the transaction |
| `account_abstraction` | `entryPointCode`, `entryPointMessage` | The ERC-4337 EntryPoint rejected the transaction. See [EntryPoint Codes](#entrypoint-codes). |
| `contract_revert` | — | A contract reverted for a reason Crossmint does not classify. Inspect `revert` and `call` to see which error and call failed. |
| `unknown` | — | The chain returned no revert data. Check `call`, replay at `blockNumber`, or inspect the transaction in a block explorer. |

`required` and `available` are decimal strings in the token's smallest unit.

Crossmint returns an `insufficient_*` or `out_of_gas` code only when it can confirm the cause. Otherwise the same revert is reported as `contract_revert`, with the decoded error in `revert`, so handle every code in the table.

### EntryPoint Codes

When `details.code` is `account_abstraction`, `entryPointCode` holds the EntryPoint code and `entryPointMessage` holds the EntryPoint's own message.

| `entryPointCode` | `entryPointMessage` | What to do |
| - | - | - |
| `AA13` | `initCode failed or OOG` | The wallet deployment failed. Contact Crossmint support with the transaction ID. |
| `AA21` | `didn't pay prefund` | The wallet cannot pay for gas. Fund it with native token, or enable gas sponsorship for the project. |
| `AA22` | `expired or not due` | The signer is outside its validity window: expired, or not yet valid. Check the signer's `expiresAt`. |
| `AA23` | `reverted` | The wallet rejected the transaction during validation. Verify the signer is registered and active on the wallet. |
| `AA24` | `signature error` | The signature is invalid. Confirm you signed the correct `message` with the transaction's signer. |
| `AA25` | `invalid account nonce` | Another transaction used the same nonce. Resubmit the transaction. |
| `AA30`–`AA36`, `PostOpReverted` | `Gas sponsorship failed` | Gas sponsorship failed and no further detail is returned. Contact Crossmint support with the transaction ID. |

### Block Explorer

If the transaction was included onchain, you can check its status using a block explorer.
