Skip to content
Development
Command

/debug-user-tx

Replay a user's failing transaction on forked state and map the error to source

From plugin
ai-kit
11154 skills15 agents54 commands4 MCP
> /plugin marketplace add solanabr/ai-kit
> /plugin install solana-ai-kit@stbr

Command definition

debug-user-tx.md
description: "Replay a user's failing transaction on forked state and map the error to source"

Debug the failing transaction described in $ARGUMENTS. The answer the developer needs is where in this repo it failed: the handler, constraint or `require!` with `file:line`, why it failed, and a fix. References: [testing.md](../skills/ext/solana-dev/skills/solana-dev/references/testing.md) (LiteSVM, Surfpool), [surfpool/cheatcodes.md](../skills/ext/solana-dev/skills/solana-dev/references/surfpool/cheatcodes.md), [transactions-v1.md](../skills/ext/solana-dev/skills/solana-dev/references/transactions-v1.md).

Inputs

| Input | Notes | |---|---| | `signature` | Preferred: fetch and diagnose in seconds. If the user only pasted an error message, ask for it first. | | `instruction` + `wallet` | When the tx never landed: the serialized tx, or accounts plus data. | | `cluster`, `rpc` | Default from `Anchor.toml` `[provider]` or `.env`. Public RPCs rate-limit, so prefer the project's provider (e.g. Helius). | | `program` | Default: IDs from `declare_id!` / `Anchor.toml`. |

Modes

  • **A, signature given:** steps 1-4 and 6. Replay (5) only when the on-chain evidence is inconclusive.
  • **B, never landed:** build the tx from the instruction, replay it (5), then map the error from the replay logs (3-4) and check state (6).
  • **C, failing program is not in this repo** (Jupiter, Token program...): skip source mapping, diagnose from logs and account state, and say the root cause is outside this codebase.

Steps

1. **Fetch** with `getTransaction` and `{"encoding":"json","maxSupportedTransactionVersion":1,"commitment":"confirmed"}`. The version must be the integer `1`: with `0` or omitted, v1 transactions fail with `-32015`. Cache the response as `.claude/debug/tx-<first 8 chars of sig>.json` and re-read it from there. Use `meta.err`, `meta.logMessages`, pre/post balances and token balances, `meta.innerInstructions`, `slot`, `message.accountKeys`, `message.instructions`, and `message.transactionConfig` (v1 transactions carry the CU limit and priority fee there instead of ComputeBudget instructions). 2. **Failing instruction:** the index is `meta.err.InstructionError[0]`; don't go by position (legacy and v0 txs usually start with ComputeBudget instructions). For v0 txs with lookup tables, index into `accountKeys ++ meta.loadedAddresses.writable ++ meta.loadedAddresses.readonly`, or CPI-heavy txs resolve the wrong program. A program ID that is not ours means mode C. 3. **Handler:** instruction `data` is base58 in `json` encoding; decode it first. Anchor: match the first 8 bytes against `instructions[].discriminator` in `target/idl/<program>.json` (this also covers custom discriminators). Pinocchio/native: usually the first byte, matched against the `process_instruction` dispatch. 4. **Error to source**, logs first:

  • `AnchorError thrown in <file>:<line>` is the exact location; quote it.
  • `AnchorError caused by account: <name>` points at a constraint on that `#[derive(Accounts)]` field; the `Left:` / `Right:` lines show the compared values.
  • The program's own `Program log:` lines and `Program <id> failed: <reason>`.

Then map `Custom(N)` for Anchor programs:

| Code | Meaning | |---|---| | 100-103 | Instruction missing, fallback not found, (de)serialization failed: client and program out of sync | | 2000s | Constraints: 2000 mut, 2001 has_one, 2002 signer, 2003 `constraint =`, 2004 owner, 2005 rent-exempt, 2006 seeds, 2011 close, 2012 address, 2014 token mint, 2015 token owner, 2016-2018 mint authority / freeze authority / decimals, 2019 space, 2021-2023 token program | | 2500-2506 | `require!` family (`require_eq!`, `require_keys_eq!`, `require_gt!`, ...) | | 3000s | Accounts: 3002 discriminator mismatch, 3003 did not deserialize, 3005 not enough keys, 3006 not mutable, 3007 owned by wrong program, 3010 not signer, 3012 not initialized | | 4100, 4102 | `DeclaredProgramIdMismatch`, `InvalidNumericConversion` | | 6000+ | The program's `#[error_code]` variants, at `offset` (default 6000) + discriminant: the variant's explicit `= N` if it sets one, else the previous variant's + 1. A second enum compiles fine and also defaults to 6000, so check every enum in the program — two whose ranges overlap decode to the same code. Report the variant, its `#[msg]` text and `file:line`. |

Pinocchio/native: map `Custom(N)` to the program's error enum or constants; standard `ProgramError` variants map by name. 5. **Replay** (optional in A, required in B). Run `surfpool start --rpc-url <rpc> --no-tui --no-deploy --skip-signature-verification --log-bytes-limit 0` in the background (`--network devnet` for devnet). `--no-deploy` keeps the on-chain program the user hit; drop it later to test the local fix. Execute with `simulateTransaction` (`sigVerify: false`, `replaceRecentBlockhash: true`, `encoding: "base64"`) or `surfnet_profileTransaction` (logs, CU, pre/post account snapshots); the kit's Surfpool MCP server exposes the same cheatcodes.

  • The fork fetches current state, not state at the failing slot. If the replay passes, the state changed since: rebuild the pre-state with `surfnet_setAccount` / `surfnet_setTokenAccount`, and `surfnet_timeTravel` for clock-dependent checks.
  • LiteSVM, when Surfpool is unavailable or to lock the fix in as a test: load the accounts plus `target/deploy/<name>.so`. Take the accounts from `surfnet_exportSnapshot` with `{"scope":{"preTransaction":"<sig>"}}` after sending the tx to the fork with `sendTransaction` (swap in a fresh blockhash; signatures are not checked), which gives the exact pre-state, or from `getAccountInfo` (current state, same caveat).

6. **Account state:** for each writable account of the failing instruction check owner, rent-exempt lamports, initialization (non-zero data, expected discriminator) and stale fields such as timestamps or oracle prices.

Common pitfalls

<!-- Adapted from sendaifun/solana-new (debug-pr

Read more
@solanabr
Ships withai-kit

Claude Code, set up to ship on Solana. Not programs alone — the whole lifecycle: validating the idea, building the program and the app around it, shipping it, and the launch and growth work that follows.

1113 skills15 agents4 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
111
Star history of solanabr/solana-ai-kit on GitHub, a chart by star-history.com

Other commands on ai-kit.

add-skill

add-skill

Command

Install a pinned skill extension on demand (/add-skill <id>), or list core packs and extensions

@solanabr@solanabrInstall Command
adv

adv

Command

Short for /adversarial: run every task through proposer and adversary pairs, no triage

@solanabr@solanabrInstall Command
adversarial

adversarial

Command

Put a task's load-bearing claims and key decisions through a proposer and an adversary, with proof

@solanabr@solanabrInstall Command
audit-solana

audit-solana

Command

Audit Solana program code for exploitable bugs and write a findings report

@solanabr@solanabrInstall Command
benchmark

benchmark

Command

Compare per-instruction CU with the stored baseline to catch regressions

@solanabr@solanabrInstall Command

More commands from other plugins.

build
Auto-invoked

build

Command

Implement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

@addyosmani@addyosmaniInstall Command