
Going Beyond Vanity Metrics
When it comes to measuring real crypto user adoption, vanity metrics- like active addresses and transaction counts, don’t tell the whole story. We have to go beyond quantity to measure quality and user behaviour.

A Primer on Smart Accounts
From EOAs to Programmable Smart Accounts: What They Are, How They Work, and Why It Matters
Leverage onchain data to discover what people are doing in crypto, why and how to maximize the trend.

Going Beyond Vanity Metrics
When it comes to measuring real crypto user adoption, vanity metrics- like active addresses and transaction counts, don’t tell the whole story. We have to go beyond quantity to measure quality and user behaviour.

A Primer on Smart Accounts
From EOAs to Programmable Smart Accounts: What They Are, How They Work, and Why It Matters
Leverage onchain data to discover what people are doing in crypto, why and how to maximize the trend.
Subscribe to Onchain Curiosity
Subscribe to Onchain Curiosity
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers


You've probably followed my guide on working with raw transaction data and maybe even decoded 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…

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.
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.
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.
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.
Let's examine a USDT token transfer with this transaction hash: 0x85d6fa5ba09b882189fb3100c55aae198a6fac963508cbf45b5056a8ee8587d0
Etherscan can be tricky, so we will use Phalcon Blocksec to see a more organized and cleaner way to walk through the decoding process.

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].
Just like decoding 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
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, where we decoded a more complex transaction.
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
You've probably followed my guide on working with raw transaction data and maybe even decoded 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…

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.
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.
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.
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.
Let's examine a USDT token transfer with this transaction hash: 0x85d6fa5ba09b882189fb3100c55aae198a6fac963508cbf45b5056a8ee8587d0
Etherscan can be tricky, so we will use Phalcon Blocksec to see a more organized and cleaner way to walk through the decoding process.

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].
Just like decoding 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
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, where we decoded a more complex transaction.
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
In this guide, we'll explore how to use traces to analyze transaction behavior, and gain a deeper understanding of smart contract execution.
Onchain Curiosity
Onchain Curiosity
1 comment
In this guide, we'll explore how to use traces to analyze transaction behavior, and gain a deeper understanding of smart contract execution.