Simulating Transactions

Simulating Transactions

POST /v2/transaction-simulations runs a transaction against the cluster without submitting it. It's the cheapest way to catch instruction errors, estimate compute budget, and confirm post-state token balances before you commit to a signature.

Request shape

curl -X POST https://api.piratecrew.fun/v2/transaction-simulations \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction": "AQABAo...<base64 VersionedTransaction>...",
    "sig_verify": false
  }'
FieldTypeDefaultNotes
transactionstring (base64)requiredA serialized VersionedTransaction. Signed or unsigned.
sig_verifybooleanfalseIf true, the cluster verifies signatures before simulating. Keep false for pre-flight on unsigned transactions.

Response shape

The response wraps the standard Solana RpcResponseAndContext<SimulatedTransactionResponse>:

{
  "data": {
    "context": { "slot": 271234567 },
    "value": {
      "err": null,
      "logs": [
        "Program ComputeBudget111111111111111111111111111111 invoke [1]",
        "Program ComputeBudget111111111111111111111111111111 success",
        "Program dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN invoke [1]",
        "Program log: Instruction: ClaimCreatorTradingFee",
        "Program dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN success"
      ],
      "units_consumed": 87420,
      "return_data": null,
      "accounts": null,
      "post_balances": ["9991820000", "5097600", "1"],
      "post_token_balances": [
        {
          "account_index": 4,
          "mint": "MintAddr...",
          "owner": "OwnerPubkey...",
          "ui_token_amount": {
            "amount": "1234567",
            "decimals": 6,
            "ui_amount": 1.234567,
            "ui_amount_string": "1.234567"
          }
        }
      ]
    }
  },
  "meta": { "request_id": "req_…" }
}

The two fields you care about most:

  • data.value.errnull means the transaction would land. Anything else is a structured Solana error (InstructionError, InsufficientFundsForRent, BlockhashNotFound, etc).
  • data.value.logs — the program log stream. If a program panicked, the panic message is here. If you msg!()'d something from on-chain code, it's here.

Use cases

Pre-flight a complex tx

The DBC fee-split flow builds a single transaction that claims partner fees, swaps if needed, and distributes to N recipients. Simulating before submitting tells you up front whether the entire sequence would land — without burning a slot or a priority fee:

curl -X POST https://api.piratecrew.fun/v2/pools/POOL_ADDRESS/fee-claims \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -d '{ "kind": "split", "payer": "PAYER", "splits": [...], "mode": "unsigned" }'
# -> { "data": { "transaction": "AQA...", ... } }

curl -X POST https://api.piratecrew.fun/v2/transaction-simulations \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -d '{ "transaction": "AQA...", "sig_verify": false }'

If value.err is null and units_consumed is reasonable, sign and submit. If not, fix and re-build.

Debug an InstructionError

When value.err looks like:

{
  "err": {
    "InstructionError": [
      1,
      { "Custom": 6001 }
    ]
  }
}

The first element (1) is the instruction index that failed. The second is the program's custom error code — for Anchor programs, look up 6001 in the program's errors.rs (e.g. 6001 = NotEnoughBalance). The logs array usually surfaces the Anchor error name too:

"Program log: AnchorError occurred. Error Code: NotEnoughBalance. ..."

Fix the inputs (more SOL in the payer, correct PDA, etc) and re-simulate.

Estimate compute units for a priority fee

value.units_consumed is the actual CU spend in the simulated context — perfect for sizing a compute-budget instruction. A typical pattern:

  1. Simulate the tx with no compute-budget instruction.
  2. Read units_consumed — call it cu.
  3. Build the real tx with ComputeBudgetProgram.setComputeUnitLimit({ units: Math.ceil(cu * 1.1) }) and a priority-fee instruction tuned to current network conditions.
  4. Re-simulate to confirm the new limit is enough.

The 10% buffer absorbs noise from cluster state changes between simulation and submission.

Verify post-state token balances

post_token_balances shows what each token account would look like after the simulated transaction. Great for confirming that:

  • The expected number of tokens lands in the recipient ATA.
  • A swap's slippage didn't tip past your tolerance.
  • A burn actually decremented the supply by the right amount.

This is cheaper than submitting, polling for confirmation, then re-fetching all the accounts.

Limits

  • Simulation reads against the current cluster state — not a fork. State changes between simulation and submission can still cause the real transaction to fail.
  • Simulation does not capture priority-fee competition. A simulation can pass and the real submission still get dropped if the priority fee is too low at landing time.
  • Compute budget caps still apply. A tx that exceeds the per-tx CU limit will fail both simulation and submission.

Treat simulation as a high-signal pre-flight, not a guarantee.