# An simple guide to Decoding Traces Data

*In this guide, we'll explore how to use traces to analyze transaction behavior, and gain a deeper understanding of smart contract execution.*

By [Onchain Curiosity](https://paragraph.com/@thechriscen) · 2025-05-29

---

You've probably followed my guide on [working with raw transaction data](https://thechriscen.substack.com/p/working-with-raw-blockchain-data) and maybe even [decoded event logs](https://thechriscen.substack.com/p/how-to-manually-decode-event-logs) using ABIs from Etherscan. But sometimes you need to see _more_. You need to understand the _inner workings_ of a smart contract interaction.

**_This is where Traces come in…_**

![](https://storage.googleapis.com/papyrus_images/d3d2e4b6353abdf4595ee54d815dfe5e.jpg)

Traces provide a detailed, step-by-step view of every operation performed during a transaction. They go beyond the surface level of transactions and events, giving you a deep understanding of what's happening inside the smart contract.

In this guide, we'll explore how to use traces to analyze transaction behavior, debug issues, and gain a deeper understanding of smart contract execution.

### What Are Traces?

Traces are records of the internal operations executed during a transaction.

In Ethereum, traces are detailed, internal records of all EVM execution steps that occur during a transaction. They capture every internal call, value transfer, and operation—even those that aren’t visible in the standard transaction log.

Instead of just seeing the end result of a transaction (the state changes) and events which are the summary, traces show you the process by which those changes were achieved.

More specifically, traces record _every_ internal operation during a transaction. Every time a function is called, a contract is created, or value is transferred internally, a trace records that action.

**Why are traces are valuable in exploring blockchain data?**

*   **Debugging:** Pinpoint the exact location of errors in complex smart contract interactions.
    
*   **Gas Optimization:** Identify gas-intensive operations within your smart contracts.
    
*   **Value Tracking:** Follow the flow of Ether and tokens through multiple contract calls.
    
*   **Root Cause Analysis:** Determine the precise cause of transaction failures.
    

For example, imagine a complex DeFi transaction that fails with a generic error message. Traces let you examine each internal call, identify the point of failure, and understand the specific reason for the revert.

### Understanding the Trace Structure

Each trace entry is a JSON object with the following key fields:

    {
      "type": "CALL",
      "from": "0x...",
      "to": "0x...",
      "value": "0x...",
      "gas": 2300,
      "gasUsed": 2100,
      "input": "0x...",
      "output": "0x...",
      "time": "2.373ms",
      "calls": [
        // Nested calls (if any)
      ]
    }
    
    

Let's break down the key fields:

*   `type`**:** The type of operation performed. This is crucial! Common types include:
    
    *   `CALL`: A function call.
        
    *   `CREATE`: A contract creation.
        
    *   `DELEGATECALL`: Executes code from a target contract in the context of the caller.
        
    *   `SELFDESTRUCT`: Contract destruction.
        
*   `from` **/** `to`**:** The sender and receiver addresses of the call.
    
*   `value`**:** The amount of Ether (in Wei) transferred.
    
*   `gas` **/** `gasUsed`**:** The gas limit and the actual gas used.
    
*   `input`**:** The input data (calldata) for the call, including the function selector and arguments.
    
*   `output`**:** The return data from the call.
    
*   `calls`**:** An array of nested trace entries, representing internal calls.
    

### Reading `traceAddress`

Each trace entry includes a `traceAddress`—an array of integers—that tells you how deeply nested the call is and its position among sibling calls.

Let's visualize how `traceAddress` maps to the call structure. Imagine the following scenario, where A initiates a transaction :

    A (null)  -- the transaction first input has a trace_address of []
      CALLs B (0)
        CALLs C (0,0)
        CALLs D (1)
          CALLs E (1,0)
            CALLs F (1,0,0)
          CALLs G (1,1)
      CALLs H (2)
    
    

*   `[]` **(Null/Empty Array):** The initial transaction call, initiated by an external account (A).
    
*   `[0]`**:** The first call _within_ the initial transaction. In this example, function A calls function B.
    
*   `[0, 0]`**:** The first call _within_ call `[0]`. C is being called by B.
    
*   `[1]`**:** The _second_ call within the initial transaction. H is the second top-level trace called by A
    
*   `[1, 0]`**:** This show the first function call from within the second internal call from the intial transaction
    
*   `[1, 0, 0]`**:** This shows the zero index of the zero index of the second index.
    
*   `[1, 1]`**:** A shows there is another index, or second call within the second index which originated.
    

For instance, this `traceAddress` tells you:

*   How many levels deep a particular function call has gotten, which shows function hierarchies.
    
*   Knowing which function call occured within which function call, can also help you trace the source of the error.
    

#### Practical Examples: Reading Traces Data

Let's examine a USDT token transfer with this transaction hash: [0x85d6fa5ba09b882189fb3100c55aae198a6fac963508cbf45b5056a8ee8587d0](https://etherscan.io/tx/0x85d6fa5ba09b882189fb3100c55aae198a6fac963508cbf45b5056a8ee8587d0)

Etherscan can be tricky, so we will use [Phalcon Blocksec](https://app.blocksec.com/explorer/tx/eth/0x85d6fa5ba09b882189fb3100c55aae198a6fac963508cbf45b5056a8ee8587d0) to see a more organized and cleaner way to walk through the decoding process.

![](https://storage.googleapis.com/papyrus_images/36bc59a69b6fe594747fc4ff0be8cf6e.png)

At a glance, you'll observe the transaction has a trace address of `[0,1]`. First, observe the sender, initiating the call. Now we see what input and output by tracing the transaction. Finally the contract emits a transfer event with the `[from, to, value]`.

### Now, let’s Decode the Trace Data Manually…

Just like [decoding event logs](https://thechriscen.substack.com/p/how-to-manually-decode-event-logs), decoding trace data means transforming the raw data into a human-readable format. We'll use a similar approach to the one in our event logs guide.

For a USDT transfer, we'll identify the `transfer` function and decode its parameters:

    transfer(address,uint256)
    
    

Here's how to do it with DuneSQL:

**1\. Extracting the Function Signature**

The first four bytes of the input data are the function signature. We can extract it using the following DuneSQL query:

    SELECT
      substring(to_hex("input"), 1, 8) as function_signature
    FROM
      ethereum.traces
    WHERE
      to = 0xdAC17F958D2ee523a2206206994597C13D831ec7  -- USDT Address
      AND tx_hash = 0x9acba5bef8645d7dd0710f831318089f5424686bc35e2741ebc37d0f307c22bc  -- transaction_hash
    
    

Let's break this down:

*   `substring(to_hex("input"), 1, 8)`: This extracts the first 8 characters (4 bytes) from the hexadecimal representation of the "input" field.
    
    *   `to_hex("input")`: This converts the binary "input" data into a hexadecimal string.
        
*   `FROM ethereum.traces`: Specifies we're querying the `ethereum.traces` table
    
*   `WHERE to = ... AND tx_hash = ...`: This narrows the query scope to USDT transfers.
    

The output will be the function signature as a hexadecimal value:

Finally, concatenate `0x` to the start of the signature, resulting in:

    transfer_signature = 0xa9059cbb
    
    

**2\. Decoding the address Parameter**

The address parameter (the recipient) is located after the function signature in the input data. Ethereum addresses are 20 bytes long, which corresponds to 40 hexadecimal characters.

To extract the address, we'll use the following DuneSQL code:

    WITH
      decoded_traces AS (
        SELECT
          regexp_extract_all(substring(to_hex("input"), 9), '.{64}') AS decoded_input
        FROM
          ethereum.traces
        WHERE
          to = 0xdac17f958d2ee523a2206206994597c13d831ec7  -- USDT Address
          AND tx_hash = 0x9acba5bef8645d7dd0710f831318089f5424686bc35e2741ebc37d0f307c22bc  -- transaction_hash
      )
    SELECT
      function_signature,
      '0x' || substring(decoded_input[1], 25, 40) AS address
    FROM
      decoded_traces
    
    

Here's the breakdown:

*   `WITH decoded_traces AS (...)`: Defines a common table expression (CTE) to preprocess the data
    
*   `regexp_extract_all(substring(to_hex("input"), 9), '.{64}') AS decoded_input`: This performs `regexp_extract_all` function to decode all the inputs from the traces table, making it easy to decode any parameter by calling its index.
    
    *   `substring(to_hex("input"),9)`: The function signature takes up the first 8 characters. To access the arguments, we access everything after. We also perform `to_hex`, so we can perform string specific operation `substring`
        
    *   `'.{64}'`: As a rule of thumb, all arguments must be decoded uint256, or has 32 bytes. Therefore, the expression above will split the input after the function into chunks of 64 characters.
        
*   `substring(to_hex("input"), 9)`: Extracts the data past the function signature.
    
*   `'0x' || substring(decoded_input[1], 25, 40)`: Extracts 40 bytes for offset.
    

**3\. Decoding the uint256 Value**

Following the address, in the input data is the value (amount) being transferred and is of `uint256` data type . `uint256` values are 32 bytes or 64 hex characters in length. The following DuneSQL code extracts and converts the value to the appropriate type:

    WITH
      decoded_traces AS (
        SELECT
          regexp_extract_all(substring(to_hex("input"), 9), '.{64}') AS decoded_input
        FROM
          ethereum.traces
        WHERE
          to = 0xdac17f958d2ee523a2206206994597c13d831ec7  -- USDT Address
          AND tx_hash = 0x9acba5bef8645d7dd0710f831318089f5424686bc35e2741ebc37d0f307c22bc  -- transaction_hash
      )
    SELECT
      varbinary_to_uint256(varbinary_substring(from_hex(decoded_input[2]), 1, 32)) AS amount
    FROM
      decoded_traces
    
    

Deconstructing the query:

*   `varbinary_to_uint256(...)`: Converts the hexadecimal value into an unsigned integer.
    
*   `varbinary_substring(from_hex(decoded_input[2]), 1, 32)`: Returns 32 bytes from `decoded_input`
    

### Putting It All Together (Example Query)

The following example shows a complete DuneSQL query with all previous steps.

    WITH
      decoded_traces AS (
        SELECT
          block_time,
          tx_hash,
          substring(to_hex("input"), 1, 8) AS function_signature,
          regexp_extract_all(substring(to_hex("input"), 9), '.{64}') AS decoded_input
        FROM
          ethereum.traces
        WHERE
          to = 0xdac17f958d2ee523a2206206994597c13d831ec7  -- USDT Address
          AND tx_hash = 0x9acba5bef8645d7dd0710f831318089f5424686bc35e2741ebc37d0f307c22bc  -- transaction_hash
      )
    SELECT
      function_signature,
      '0x' || substring(decoded_input[1], 25, 40) AS address,
      varbinary_to_uint256(varbinary_substring(from_hex(decoded_input[2]), 1, 32)) AS amount
    FROM
      decoded_traces
    
    

If you followed up to this point, I congratulate you on taking the first step towards working with traces data. To go beyond a simple USDT transfer case study, you can check out this [Dune Query,](https://dune.com/queries/5205084) where we decoded a more complex [transaction.](https://app.blocksec.com/explorer/tx/eth/0x4fa29a598d3daf1709e478fc3c3c7d712ec6988ce29393b7f3e4fc83893b191a?line=0)

### That’s a Wrap!

By understanding the structure of trace data and applying appropriate DuneSQL functions, you can efficiently decode and analyze trace data.

The best way to master this skill is to practice. Start exploring traces for transactions you find interesting, and you'll quickly develop a solid understanding of Ethereum internal workings.

Thanks for reading Onchain Curiosity! Subscribe for free to receive new posts and support my work.

Subscribe

---

*Originally published on [Onchain Curiosity](https://paragraph.com/@thechriscen/an-simple-guide-to-decoding-traces-data)*
