Receipts & Logs
Tevm Node manages transaction receipts and event logs through the ReceiptsManager and filter system — enabling event listening, status tracking, and log filtering.
Quick Start
Basic Usage
import { createTevmNode, PREFUNDED_ACCOUNTS } from 'tevm'
import { callHandler, mineHandler } from 'tevm/actions'
import { hexToBytes } from 'tevm/utils'
const node = createTevmNode()
const receiptsManager = await node.getReceiptsManager()
const callResult = await callHandler(node)({
addToMempool: true,
from: PREFUNDED_ACCOUNTS[0].address,
to: '0x2345678901234567890123456789012345678901',
value: 1000000000000000000n,
})
await mineHandler(node)({})
if (!callResult.txHash) {
throw new Error('Transaction was not added to the mempool')
}
const receiptResult = await receiptsManager.getReceiptByTxHash(
hexToBytes(callResult.txHash)
)
if (receiptResult) {
const [receipt, blockHash, txIndex, logIndex] = receiptResult
console.log({
status: 'status' in receipt ? receipt.status : undefined,
gasUsed: receipt.cumulativeBlockGasUsed,
logs: receipt.logs
})
}Receipt Types
Tevm supports receipts across all Ethereum hard forks:
- Pre-Byzantium — uses state root for transaction results
- Post-Byzantium — uses status codes (success/failure)
- EIP-4844 — includes blob gas information
interface PreByzantiumReceipt {
stateRoot: Uint8Array // Merkle root after transaction
cumulativeBlockGasUsed: bigint
logs: Log[]
// No status field
}
interface PostByzantiumReceipt {
status: number // 1 = success, 0 = failure
cumulativeBlockGasUsed: bigint
logs: Log[]
}
interface EIP4844Receipt extends PostByzantiumReceipt {
blobGasUsed: bigint
blobGasPrice: bigint
}function processReceipt(receiptResult) {
if (!receiptResult) return 'Receipt not found'
const [receipt] = receiptResult
if ('status' in receipt) {
return `Transaction ${receipt.status === 1 ? 'succeeded' : 'failed'}`
} else {
return `Transaction included with state root: 0x${Buffer.from(receipt.stateRoot).toString('hex')}`
}
}Working with Event Logs
Contract Deployment
import { createTevmNode, PREFUNDED_ACCOUNTS } from 'tevm'
import { callHandler, mineHandler } from 'tevm/actions'
import { SimpleContract } from 'tevm/contract'
import { encodeDeployData } from 'viem'
const node = createTevmNode()
const deployResult = await callHandler(node)({
addToMempool: true,
from: PREFUNDED_ACCOUNTS[0].address,
data: encodeDeployData(SimpleContract.deploy(2n)),
throwOnFail: false,
})
await mineHandler(node)({})
const contractAddress = deployResult.createdAddress
if (!contractAddress) {
throw new Error('Contract deployment failed')
}Emit Events
import { PREFUNDED_ACCOUNTS } from 'tevm'
import { callHandler, mineHandler } from 'tevm/actions'
import { SimpleContract } from 'tevm/contract'
import { encodeFunctionData } from 'viem'
const contract = SimpleContract.withAddress(contractAddress)
const callResult = await callHandler(node)({
blockTag: 'pending',
addToMempool: true,
from: PREFUNDED_ACCOUNTS[0].address,
to: contractAddress,
data: encodeFunctionData(contract.write.set(42n)),
gas: 100000n,
throwOnFail: false,
})
await mineHandler(node)({})Query Logs
import { createAddress } from 'tevm/address'
import { hexToBytes } from 'tevm/utils'
const vm = await node.getVm()
const receiptsManager = await node.getReceiptsManager()
const fromBlock = await vm.blockchain.getBlock(0n)
const toBlock = await vm.blockchain.getCanonicalHeadBlock()
const contract = createAddress(contractAddress)
// 1. All logs from the contract
const contractLogs = await receiptsManager.getLogs(
fromBlock, toBlock, [contract.toBytes()], undefined
)
// 2. Logs for a specific event
const setEventSignature = '0x012c78e2b84325878b1bd9d250d772cfe5bda7722d795f45036fa5e1e6e303fc'
const setLogs = await receiptsManager.getLogs(
fromBlock, toBlock, [contract.toBytes()], [hexToBytes(setEventSignature)]
)
// 3. Logs with wildcard topics
const wildcardLogs = await receiptsManager.getLogs(
fromBlock,
toBlock,
[contract.toBytes()],
[null]
)Process Log Data
// ReceiptsManager logs are returned with their block and transaction metadata.
for (const { log, block, txIndex, logIndex } of setLogs) {
const [address, topics, data] = log
const value = BigInt('0x' + Buffer.from(data).toString('hex'))
console.log(`Contract: 0x${Buffer.from(address).toString('hex')}`)
console.log(`Topic: 0x${Buffer.from(topics[0]).toString('hex')}`)
console.log(`Value: ${value}`)
console.log(`Block: ${block.header.number}, TxIndex: ${txIndex}, LogIndex: ${logIndex}`)
}Advanced Features
Complex Filtering
Ethereum logs support up to 4 topics:
const fromAddress = '0x1234567890123456789012345678901234567890'
const toAddress = '0x2345678901234567890123456789012345678901'
// undefined = wildcard
const topics = [
hexToBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'), // Transfer
hexToBytes('0x000000000000000000000000' + fromAddress.slice(2)),
hexToBytes('0x000000000000000000000000' + toAddress.slice(2))
]
const filteredLogs = await receiptsManager.getLogs(
fromBlock,
toBlock,
[contractAddress.toBytes()],
topics
)Multiple Addresses
const tokenAddress = createAddress('0x1234567890123456789012345678901234567890')
const marketplaceAddress = createAddress('0x2345678901234567890123456789012345678901')
const transferEvent = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
const transfers = await receiptsManager.getLogs(
fromBlock,
toBlock,
[tokenAddress.toBytes(), marketplaceAddress.toBytes()],
[hexToBytes(transferEvent)]
)
const tokenEvents = transfers.filter(log =>
Buffer.from(log.address).toString('hex') === Buffer.from(tokenAddress.toBytes()).toString('hex')
)
const marketplaceEvents = transfers.filter(log =>
Buffer.from(log.address).toString('hex') === Buffer.from(marketplaceAddress.toBytes()).toString('hex')
)Receipt Indexing
// Receipt by tx hash
const receipt = await receiptsManager.getReceiptByTxHash(txHash)
// All receipts in a block
const block = await vm.blockchain.getCanonicalHeadBlock()
const receipts = await receiptsManager.getReceipts(block.hash())
// Built-in limits
const GET_LOGS_LIMIT = 10000 // Max logs returned
const GET_LOGS_LIMIT_MEGABYTES = 150 // Max response size
const GET_LOGS_BLOCK_RANGE_LIMIT = 2500 // Max block rangeBest Practices
- Efficient Queries — use specific filters and limited block ranges
- Handle Null Results — always check for null/undefined receipts
- Type Safety — check receipt type before accessing fields
- Pagination — paginate large log queries
Efficient Log Queries
const latestBlock = await vm.blockchain.getCanonicalHeadBlock()
const blockNumber = latestBlock.header.number
// Last 100 blocks only
const fromBlock = await vm.blockchain.getBlock(
blockNumber - 100n > 0n ? blockNumber - 100n : 0n
)
const logs = await receiptsManager.getLogs(
fromBlock,
latestBlock,
[contractAddress.toBytes()],
[eventTopic]
)Proper Error Handling
async function safeGetReceipt(txHash) {
try {
const receiptResult = await receiptsManager.getReceiptByTxHash(txHash)
if (receiptResult === null) {
console.log('Receipt not found - transaction may be pending or not exist')
return null
}
const [receipt] = receiptResult
return receipt
} catch (error) {
console.error('Error retrieving receipt:', error.message)
return null
}
}Working with Receipt Types
function getTransactionStatus(receipt) {
if (!receipt) return 'Unknown'
if ('status' in receipt) {
return receipt.status === 1 ? 'Success' : 'Failed'
} else if ('stateRoot' in receipt) {
const emptyRoot = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
const actualRoot = '0x' + Buffer.from(receipt.stateRoot).toString('hex')
return actualRoot === emptyRoot ? 'Likely Failed' : 'Likely Success'
}
return 'Unknown Format'
}
