Skip to content
Development
Command

/debug-user-tx

Reproduce and debug a user-reported failing transaction against forked cluster state, mapping the failure back to source code

From plugin
solana-ai-kit
10130 skills15 agents30 commands7 MCP
Install
> /plugin marketplace add solanabr/solana-ai-kit
> /plugin install solana-ai-kit@stbr

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/debug-user-tx

Context preview

What this command does when you run it.

Reproduce and debug a user-reported failing transaction against forked cluster state, mapping the failure back to source code

Command definition

debug-user-tx.md
description: "Reproduce and debug a user-reported failing transaction against forked cluster state, mapping the failure back to source code"

You are debugging a transaction that a user reports as failing. You have the project's source code locally; the goal is to reproduce the failure against forked cluster state, then map the on-chain error back to the exact line of Rust / IDL that produced it and suggest a fix.

Related Skills

  • [ext/solana-dev/skill/references/testing.md](../skills/ext/solana-dev/skill/references/testing.md) — Surfpool (mainnet fork), LiteSVM, Mollusk
  • [ext/solana-dev/skill/references/programs/anchor.md](../skills/ext/solana-dev/skill/references/programs/anchor.md) — Anchor error codes, constraint failures
  • [ext/solana-dev/skill/references/programs/pinocchio.md](../skills/ext/solana-dev/skill/references/programs/pinocchio.md) — Pinocchio error patterns
  • [ext/solana-dev/skill/references/security.md](../skills/ext/solana-dev/skill/references/security.md) — Common failure categories

Inputs

Collect from the user (at least one of `signature` or `instruction` is required):

| Input | Required | Notes | |-------|----------|-------| | `signature` | preferred | On-chain tx signature. Fastest path — fetch + replay. | | `wallet` | optional | User's pubkey. Auto-extracted from tx if signature given. | | `instruction` | fallback | Raw ix JSON when the tx never landed (serialized tx / accounts + data). | | `cluster` | optional | `mainnet` / `devnet`. Default: infer from `Anchor.toml` or `.env`. | | `rpc` | optional | Override RPC endpoint. Default: cluster default or project `.env`. | | `program` | optional | Program ID. Auto-detected from workspace. |

If the user only pasted an error message, ask for the signature before proceeding — it's the difference between 30 seconds and guessing.

Step 1: Detect Project Layout

echo "Detecting project..."

FRAMEWORK="unknown"
if [ -f "Anchor.toml" ]; then
    FRAMEWORK="anchor"
    echo "Anchor project detected"
elif [ -f "Cargo.toml" ] && grep -q "pinocchio" Cargo.toml 2>/dev/null; then
    FRAMEWORK="pinocchio"
    echo "Pinocchio project detected"
elif [ -f "Cargo.toml" ] && grep -q "solana-program" Cargo.toml 2>/dev/null; then
    FRAMEWORK="native"
    echo "Native Solana program detected"
else
    echo "No Solana program workspace detected. Debug will proceed RPC-only (no source mapping)."
fi

# Infer cluster
CLUSTER="${CLUSTER:-mainnet}"
if [ -f "Anchor.toml" ]; then
    DETECTED=$(grep -m1 'cluster' Anchor.toml | sed 's/.*= *//' | tr -d '"')
    [ -n "$DETECTED" ] && CLUSTER="$DETECTED"
fi
echo "Cluster: $CLUSTER"

# Collect program IDs + IDLs
ls target/idl/*.json 2>/dev/null || echo "No IDLs found at target/idl/ — run 'anchor build' for best results"

Step 2: Fetch the Failing Transaction

If a signature was provided, fetch it with full detail. Use the project's RPC if configured; otherwise fall back to the cluster default.

SIG="<signature>"
RPC="${RPC:-https://api.$CLUSTER.solana.com}"

mkdir -p .claude/debug
OUT=".claude/debug/tx-${SIG:0:8}.json"

curl -s -X POST "$RPC" \
  -H "Content-Type: application/json" \
  -d "$(cat <<EOF
{
  "jsonrpc":"2.0","id":1,"method":"getTransaction",
  "params":["$SIG",{"encoding":"json","maxSupportedTransactionVersion":0,"commitment":"confirmed"}]
}
EOF
)" > "$OUT"

# Sanity check
jq -e '.result != null' "$OUT" >/dev/null || { echo "Tx not found or not yet confirmed"; exit 1; }

Extract from the JSON (Claude: read `$OUT` with `jq` or the Read tool):

  • `meta.err` — the error object (e.g. `{"InstructionError":[1,{"Custom":6003}]}`)
  • `meta.logMessages` — full program log output
  • `meta.preBalances` / `meta.postBalances`
  • `meta.preTokenBalances` / `meta.postTokenBalances`
  • `meta.innerInstructions` — CPI tree
  • `slot` — for fork replay
  • `transaction.message.accountKeys` — ordered account list
  • `transaction.message.instructions` — each with `programIdIndex`, `accounts`, `data`
  • `transaction.message.addressTableLookups` — resolve if present

Step 3: Identify the Failing Instruction

From `meta.err`, extract the instruction index. For `InstructionError: [N, ...]`, instruction `N` failed.

FAILING_IX=$(jq -r '.result.meta.err.InstructionError[0]' "$OUT")
FAILING_PROGRAM=$(jq -r --argjson i "$FAILING_IX" \
  '.result.transaction.message.accountKeys[.result.transaction.message.instructions[$i].programIdIndex]' \
  "$OUT")
echo "Failing instruction #$FAILING_IX → program $FAILING_PROGRAM"

> **Note**: Index 0 is almost always `ComputeBudget` (`setComputeUnitLimit` / `setComputeUnitPrice`). The real failure is usually index ≥ 1. Trust `meta.err.InstructionError[0]`, not position.

> **Address Table Lookups**: If `transaction.message.addressTableLookups` is non-empty, extend the account list before indexing: `accountKeys ++ meta.loadedAddresses.writable ++ meta.loadedAddresses.readonly`. Otherwise you'll resolve the wrong program for CPI-heavy txs.

Compare `$FAILING_PROGRAM` against the project's program IDs (from `declare_id!` or `Anchor.toml`). If it matches, source mapping is possible. If not (e.g. Jupiter, Token Program), the failure is inside a CPI target — note this and proceed with log-based diagnosis.

Decode the instruction discriminator

`instructions[N].data` is **base58-encoded** when fetched with `encoding: "json"` (what Step 2 uses). Decode before slicing.

For the project's own program:

  • **Anchor**: first 8 bytes of decoded data = handler discriminator.
  • Anchor ≥ 0.30: match directly against `target/idl/<program>.json` → `instructions[].discriminator`.
  • Anchor < 0.30: IDL has no `discriminator` field. Compute it: `sha256("global:<handler_name>")[0..8]`, then match.
  • **Pinocchio / native**: typically first 1 byte. Match against the `match` arm in `process_instruction` (grep `src/lib.rs` or `src/entrypoint.rs`).

Record the handler name.

Step 4: Map the Error to Source

**Fast path — scan `meta.logMessages`

Read more
Ships withsolana-ai-kit

Production-ready Claude Code configuration for full-stack Solana development. Combines best practices from multiple sources into an agent-optimized, token-efficient config you can install and adapt to your specific project.

Get the whole plugin

Other commands on solana-ai-kit.