On-chain data and SQL · Lesson 4 of 9
Raw Ethereum transactions, receipts and logs
8 min read
On Ethereum and other EVM chains, a transaction records what a sender asked for, while the receipt, the logs and the traces record what actually happened. Token movements, failed calls and value moved by contracts never appear in the transaction itself. An analyst who reads only transactions misses most of the activity on the chain; one who knows the four objects can say precisely what moved, who triggered it and whether it succeeded.
Transaction fields and the five types
Since EIP-2718 (2021), an Ethereum transaction is a typed envelope: one type byte followed by a payload whose fields depend on the type. Legacy transactions, which predate the envelope, count as type 0.
| Type | Introduced | Fee fields | Additional fields |
|---|---|---|---|
0x0 legacy | Original; chain ID added by EIP-155 (2016) | gasPrice | Chain ID folded into v |
0x1 access list | EIP-2930, Berlin (April 2021) | gasPrice | accessList |
0x2 dynamic fee | EIP-1559, London (August 2021) | maxFeePerGas, maxPriorityFeePerGas | accessList |
0x3 blob | EIP-4844, Dencun (March 2024) | As type 2, plus maxFeePerBlobGas | blobVersionedHashes |
0x4 set code | EIP-7702, Pectra (May 2025) | As type 2 | authorizationList |
All types share nonce (the sender's transaction counter), gas (the gas limit), to, value (in wei), input (call data) and a signature (v or yParity, r, s). Note that from is not a field: nodes recover the sender from the signature and add it to API responses. In a legacy transaction the chain ID is encoded in v as chainId × 2 + 35 or 36; typed transactions carry an explicit chainId.
Two newer types matter for analysts:
- Blob transactions (type 3) carry large data blobs for rollups. The blobs are not part of the execution block: they travel as consensus-layer sidecars that nodes must keep for only 4,096 epochs, about 18 days. The transaction keeps only each blob's versioned hash (first byte
0x01). A type 3 transaction must have atoaddress, so it cannot create a contract. - Set-code transactions (type 4) let an externally owned account (EOA) delegate to contract code. Each entry of the authorization list is signed by the account owner and contains
chainId,address,nonce,yParity,rands. After processing, the account's code is the 23-byte delegation designator0xef0100followed by the delegate's address; delegating to the zero address clears it. From then on, calls to that EOA run the delegate's code, and the account that signed the authorization need not be the one that paid gas. Malicious delegations have been used to drain wallets, soeth_getCodereturning0xef0100…for an apparently ordinary address is worth noting. The lesson The account model and reconstructing balances discusses the consequences.
Here is a type 2 transaction as eth_getTransactionByHash returns it, with long values truncated:
{
"type": "0x2", "chainId": "0x1", "nonce": "0x2a",
"hash": "0x7e4b…a3c0", "blockNumber": "0x1406f52", "transactionIndex": "0x5",
"from": "0xa1b2…009f", "to": "0x5d3a…7c10", "value": "0x0",
"gas": "0x186a0", "maxFeePerGas": "0x9502f9000", "maxPriorityFeePerGas": "0x3b9aca00",
"input": "0xa9059cbb000000000000000000000000c3d4…22e1000…009502f900",
"accessList": [], "yParity": "0x1", "r": "0x8f1e…", "s": "0x2c07…"
}
All quantities are hexadecimal strings: the gas limit 0x186a0 is 100,000, the maximum fee 0x9502f9000 is 40,000,000,000 wei (40 gwei) per gas and the tip 0x3b9aca00 is 1 gwei. The value is zero and to is a contract: this is a call, not an ETH payment.
Receipts and status
A receipt is produced when the transaction executes. eth_getTransactionReceipt returns:
| Field | Meaning |
|---|---|
status | 0x1 success or 0x0 failure (EIP-658, Byzantium, October 2017; older receipts carry root instead) |
gasUsed, cumulativeGasUsed | Gas used by this transaction, and by the block up to and including it |
effectiveGasPrice | Price actually paid per gas |
logs, logsBloom | Events emitted, and their Bloom filter |
contractAddress | Address created, for deployments; otherwise null |
blobGasUsed, blobGasPrice | Present for blob transactions |
type, from, to, blockHash, blockNumber, transactionIndex | Identifiers |
For a type 2 transaction the effective price is the lower of maxFeePerGas and base fee plus maxPriorityFeePerGas. If our example executed with a 12 gwei base fee and used 52,000 gas, the price was min(40, 12 + 1) = 13 gwei and the fee 52,000 × 13 = 676,000 gwei, or 0.000676 ETH. A blob transaction also pays blobGasUsed × blobGasPrice.
When status is 0x0, the value did not move, every log was discarded and the state changes were undone, but the nonce was consumed and the fee paid. status describes only the transaction as a whole: an inner call can fail while the transaction succeeds, which only traces reveal.
Logs and topics
A log (event) is a record a contract writes during execution. Each has:
address: the contract that emitted it;topics: zero to four 32-byte values;data: the remaining ABI-encoded bytes;- position fields:
logIndex(position in the block),transactionHash,transactionIndex,blockNumber,blockHash, andremoved, true when a reorg dropped the log.
For a standard (non-anonymous) Solidity event, topics[0] is the Keccak-256 hash of the event signature and the next topics hold up to three indexed parameters. The ERC-20 Transfer event decodes like this:
{
"address": "0x5d3a…7c10",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000a1b2…009f",
"0x000000000000000000000000c3d4…22e1"
],
"data": "0x000000000000000000000000000000000000000000000000000000009502f900",
"logIndex": "0x1c", "removed": false
}
topics[0] is keccak256 of Transfer(address,address,uint256). Topics 1 and 2 are the from and to addresses, left-padded to 32 bytes: drop the first 12 bytes to recover them. data holds the unindexed value: 0x9502f900 is 2,500,000,000 raw units, which is 2,500 tokens if the contract reports 6 decimals. Always check address. Anyone can deploy a contract that emits a perfectly formed Transfer event, and fake tokens imitating well-known ones do exactly that; the event is only as meaningful as the contract that wrote it.
Input data and ABI decoding basics
The input field tells a contract which function to run. Its first 4 bytes are the function selector, the start of the Keccak-256 hash of the function signature: transfer(address,uint256) gives 0xa9059cbb. The arguments follow in 32-byte words under the ABI encoding rules: here, the padded recipient c3d4…22e1, then the amount 0x9502f900.
Decoding needs the contract's ABI (its interface description), published with verified source code or rebuilt from public selector databases. Selectors are only 4 bytes, so different signatures can collide, and a database match is a hypothesis rather than proof. Treat input as the request and logs, traces and state as the outcome: a call to transfer that reverted moved nothing. The lesson Decoding smart contract interactions covers proxies, multicall and verified source.
Traces for internal calls
When a contract calls another contract or sends ETH, that internal call is not a transaction and produces no log unless the code emits one. It is visible only by re-executing the transaction with tracing. Two output styles exist:
- Geth-style
debug_traceTransactionwith thecallTracerreturns a nested tree of call frames, each withtype(CALL, STATICCALL, DELEGATECALL, CREATE, CREATE2, SELFDESTRUCT),from,to,value,gas,gasUsed,input,output,errorand childcalls. - Parity-style
trace_transactionandtrace_block, offered by clients such as Erigon, Nethermind and Reth, return a flat list in whichtraceAddressgives each call's position in the tree andaction.callTypeits kind.
Traces answer questions nothing else can: ETH paid out by a contract, which subcall failed, and in what context code ran (a DELEGATECALL executes another contract's code against the caller's storage and balance). Tracing old transactions needs a node that can rebuild historical state, typically an archive node. Filtering out frames that carry an error, and their children, prevents counting value that never moved; the lesson Querying UTXO data and EVM logs and traces shows how to do this in SQL.
Putting the four objects together
Reading the running example end to end shows how the objects complement each other. The transaction says that 0xa1b2…009f asked contract 0x5d3a…7c10 to run transfer for recipient c3d4…22e1 and 2,500,000,000 raw units, paying no ETH. The receipt says the request succeeded (status 0x1) and cost 0.000676 ETH. The log, emitted by the same contract, confirms that the token's balances changed: 2,500 tokens from a1b2…009f to c3d4…22e1. Because the call was a direct token transfer, a trace would show only one frame and add nothing.
Change one detail and the reading changes. Had the user called a DEX router instead, the transaction would show only the router address and a swap request; the logs would show several Transfer events from different token contracts; and any ETH paid out would appear only in the traces. The rule of thumb: start from the transaction, check the receipt's status, take movements of tokens from logs and movements of ETH from value and traces.
Exam focus: the five transaction types (0 to 4), their EIPs, upgrades and distinguishing fields; thatfromis recovered from the signature; blob transactions, sidecars and the roughly 18-day blob retention; EIP-7702 authorizations and the0xef0100delegation designator; receipt fields,status, the effective gas price and what a failed transaction still costs; log structure,topics[0]as the event signature hash and decoding the Transfer event; function selectors and why selector matches are hypotheses; Geth-style and Parity-style traces for internal calls.