Skip to content

Execution traces ​

An execution trace is the expression-by-expression record of how a transaction executed. It is the data behind the call tree in the stxer inspector.

Traces are available for:

  • on-chain transactions: GET /inspect/{block_height}/{block_hash}/{txid}
  • transaction steps in a simulation session: GET /devtools/v2/simulations/{id}/inspect/{txid}

A trace exists only if the transaction ran Clarity code. A plain STX transfer has none, nor does a call rejected before it started, an Eval step, or a session created with skip_tracing: true. Those return 404.

What is in a trace ​

A trace is a tree. The root is the function the transaction called; each node is one evaluated expression and holds:

FieldMeaning
codeWhere it ran; a node names its contract where a function is entered, e.g. SP102V8….amm-pool-v2-01:swap-helper
funcThe expression: contract-call?, map-get?, if, a user function, …
args, resultDecoded Clarity values, or a string when the expression produced no value (a runtime error, or an early return such as EarlyReturn(UnwrapFailed(…)) travelling up)
costsThe five cost counters before and after the node: runtime, read_count, read_length, write_count, write_length
idThe id of the expression in the contract's AST, to map back to source
childrenThe expressions it evaluated in turn

So a trace answers three questions:

  • Internal calls. Every contract-call? is a node, with the callee's nodes beneath it, so the tree shows which contracts called which and with what arguments.
  • Reads and writes. Data-var, map and token operations are nodes with their arguments and results, and the read and write counters show how much storage each subtree touched.
  • Costs. The difference between a node's before and after counters is its inclusive cost; subtracting the children gives its own cost. Own costs add up to the whole transaction exactly once.

An abbreviated example, from mainnet transaction 0xc1bfb9616c51a17859c48f1716eefdd5ea9646f59b8e6c082033370ae17e33fa (740 nodes, 7 contracts):

if                      amm-pool-v2-01:swap-helper   (ok u112248824)    runtime 540244
  is-some                                            false              runtime 43721
    get-pool-exists                                  none               runtime 43510
      contract-of                                    …token-wkiki       runtime 13416
      contract-of                                    …token-alex        runtime 13416
      contract-call?    amm-pool-v2-01:get-pool…     none               runtime 15906
        map-get?        amm-registry-v2-01           none               runtime 3705

Root totals: runtime 540244, read_count 97, read_length 175494, write_count 6, write_length 657.

Viewing traces ​

Open a transaction in the inspector and pick Debugger:

https://stxer.xyz/tx/mainnet/0xc1bfb9616c51a17859c48f1716eefdd5ea9646f59b8e6c082033370ae17e33fa/debugger

For a simulated transaction: https://stxer.xyz/simulations/{network}/{simulation_id}/txid/{txid}.

Reading traces in code ​

The API returns a compressed binary blob (application/octet-stream). The SDK fetches and decodes it:

ts
import { getTransactionTrace, flattenTrace, traceNodeCost } from 'stxer'

const trace = await getTransactionTrace({
  blockHeight: 1018838,
  blockHash: '0x89945f0d9956794d453adb28d78302002a1d16885d8b4de3ee598ec69c631a54',
  txid: '0xc1bfb9616c51a17859c48f1716eefdd5ea9646f59b8e6c082033370ae17e33fa',
})

for (const { node, depth, contractId } of flattenTrace(trace.root)) {
  console.log(`${'  '.repeat(depth)}${node.func} ${contractId} runtime=${traceNodeCost(node).runtime}`)
}

blockHash is the Stacks block hash (block_hash in the Stacks API), not the index_block_hash. See SDK: traces for walking the tree, own costs and mapping nodes to source.