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:
| Field | Meaning |
|---|---|
code | Where it ran; a node names its contract where a function is entered, e.g. SP102V8….amm-pool-v2-01:swap-helper |
func | The expression: contract-call?, map-get?, if, a user function, … |
args, result | Decoded Clarity values, or a string when the expression produced no value (a runtime error, or an early return such as EarlyReturn(UnwrapFailed(…)) travelling up) |
costs | The five cost counters before and after the node: runtime, read_count, read_length, write_count, write_length |
id | The id of the expression in the contract's AST, to map back to source |
children | The 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
beforeandaftercounters 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 3705Root 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/debuggerFor 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:
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.