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

# Transaction Debugging

> Work out why a Solana transaction failed, was rejected before sending, or never landed: reading simulation errors and program logs, blockhash expiry, encoding mistakes, and priority fees.

A transaction ends up in one of three places, and each needs a different fix. Find out which one before changing anything.

## Step 1: find out what happened

Ask the cluster about the signature. `searchTransactionHistory` makes the lookup work for transactions of any age.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["SIGNATURE"],{"searchTransactionHistory":true}]}'
```

| Result | What it means | Go to |
| - | - | - |
| `"err": null` with `"confirmationStatus": "confirmed"` or `"finalized"` | It landed and succeeded | Nothing to debug. If the outcome is still wrong, the instructions did something other than you expected |
| `"err": { ... }` | It landed and **failed**. The fee was charged and nothing else changed | [Landed but failed](#landed-but-failed) |
| `null` | The cluster has never seen it | [Never landed](#never-landed) |

If you got an error back from `sendTransaction` itself, there is no signature to look up. The transaction was rejected before it was sent. Go to [Rejected before sending](#rejected-before-sending).

To check signatures from code, use the [wait for confirmation recipe](/recipes#wait-for-a-transaction-to-confirm).

## Rejected before sending

By default `sendTransaction` simulates the transaction first and refuses to send it if the simulation fails. You get error `-32002` with the reason and the program logs:

```json theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
{
  "code": -32002,
  "message": "Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1",
  "data": {
    "err": { "InstructionError": [0, { "Custom": 1 }] },
    "logs": [
      "Program 11111111111111111111111111111111 invoke [1]",
      "Transfer: insufficient lamports 60773561, need 1000000000000",
      "Program 11111111111111111111111111111111 failed: custom program error: 0x1"
    ],
    "unitsConsumed": 150
  }
}
```

Read it in this order:

1. **`data.logs`** usually says what went wrong in plain words. Here, the payer has 0.06 SOL and tried to send 1,000.
2. **`data.err`** says which instruction failed. `InstructionError: [0, ...]` is the first instruction, counting from zero.
3. **The custom error code** belongs to the program that failed, not to Solana. `Custom: 1` from the System Program is "insufficient funds", but `Custom: 1` from another program means something else. Look the number up in that program's error list (its IDL, or its source). The message shows it in hex, so `0x1770` is `6000`, the first custom error in an Anchor program.

A rejection costs nothing. The transaction never left the RPC node.

### Common rejections

| Error | Cause | Fix |
| - | - | - |
| `Transaction simulation failed: Blockhash not found` | The blockhash is expired, from another cluster, or made up | Fetch a new one with `getLatestBlockhash` right before signing |
| `Transaction simulation failed: Attempt to debit an account but found no record of a prior credit` | The fee payer has never held SOL on this cluster | Fund the fee payer. On devnet, check you are not using a mainnet wallet with no devnet balance |
| `custom program error: 0x1` from the System Program | Not enough lamports for the transfer plus fees | Check the balance, including rent for any accounts the transaction creates |
| `exceeded CUs meter at BPF instruction` | The compute unit limit is too low | Simulate, read `unitsConsumed`, and set the limit about 10% above it |
| `invalid base58 encoding: InvalidCharacter` | You sent base64 bytes without saying so. `sendTransaction` assumes base58 | Pass `{"encoding": "base64"}` as the second parameter |
| `failed to deserialize ... VersionedTransaction: io error: failed to fill whole buffer` | The bytes are not a complete transaction: truncated, double-encoded, or unsigned | Serialize the signed transaction once and encode it once |

## Debug with simulateTransaction

When you want the logs without sending anything, simulate. Two options make it far more useful while debugging:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["BASE64_TX",{"encoding":"base64","replaceRecentBlockhash":true,"sigVerify":false}]}'
```

* **`replaceRecentBlockhash: true`** swaps in a current blockhash, so you can keep simulating the same transaction after its blockhash expires. It cannot be combined with `sigVerify: true`.
* **`sigVerify: false`** skips signature checks, so you can simulate an unsigned or partially signed transaction.

The result has `err`, `logs` and `unitsConsumed` even when the simulation succeeds. Use `unitsConsumed` to set an accurate compute unit limit: a tight limit makes the priority fee you pay per unit go further.

> [!WARNING]
>
> Avoid `skipPreflight: true` while debugging. It sends the transaction without simulating, so a broken transaction lands, fails and charges the fee, and the error only appears later in `getSignatureStatuses`. Turn it on once the transaction is known to work and you need the speed.

## Landed but failed

The transaction is on chain, the fee is gone, and none of its changes took effect. Get the full record:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getTransaction","params":["SIGNATURE",{"encoding":"json","maxSupportedTransactionVersion":1}]}'
```

`meta.err` has the same shape as a simulation error and `meta.logMessages` has the program logs. Read them as described in [Rejected before sending](#rejected-before-sending).

A transaction that simulated fine can still fail on chain because state changed in between. The usual culprits are slippage on a swap, an account someone else closed or modified, or a nonce that was already used. Re-simulate against current state to confirm, then build in tolerance: wider slippage, a fresh quote, or a retry that rebuilds the transaction.

## Never landed

`getSignatureStatuses` returns `null`: the transaction was accepted by the RPC node but no leader included it. Nothing was charged.

### The blockhash expired

A transaction is only valid for **150 blocks** after its blockhash. With slots taking about 215 ms, that is roughly 30 to 35 seconds. Once it passes, the transaction can never land, so stop waiting and rebuild it.

Check whether a blockhash is still usable:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"isBlockhashValid","params":["BLOCKHASH",{"commitment":"processed"}]}'
```

Save the `lastValidBlockHeight` that `getLatestBlockhash` returns alongside your blockhash. Compare it with `getBlockHeight`: while the block height is lower, keep resending the same signed transaction. Once it is higher, the transaction is dead.

### The priority fee was too low

When blocks are busy, leaders fill them with the transactions that pay most per compute unit. Check what recently landed for the accounts you touch:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getRecentPrioritizationFees","params":[["ACCOUNT_YOUR_TX_WRITES"]]}'
```

Pick a value from the recent results, set it with `SetComputeUnitPrice`, and pair it with an accurate `SetComputeUnitLimit` from simulation.

### It was dropped on the way

A sent transaction can be lost between the RPC node and the leader, especially during congestion. Resend the same signed bytes every couple of seconds until it confirms or the blockhash expires. Resending is safe: the same signature can only land once.

For transactions where landing quickly matters, send through [Apex](/apex/overview). Apex sends each transaction to upcoming leaders over several paths at once and keeps retrying until it lands or expires. Its own errors are listed in [Apex errors and rate limits](/apex/errors-and-rate-limits).

## Still stuck

Collect the signature (or the base64 transaction if it never got one), the full error including `data.logs`, the region you sent through, and whether preflight was on. Then ask in [Discord](https://discord.gg/orbitflare).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.