Skip to main content

debug_traceTransaction

Returns a detailed execution trace of a transaction, showing all internal calls, state changes, and gas consumption. Essential for debugging complex smart contract interactions.

Premium Method

This method requires enhanced API access. Please contact support for access to debug methods.

When to Use This Method​

debug_traceTransaction is crucial for:

  • Smart Contract Debugging - Analyze transaction execution step-by-step
  • Gas Optimization - Identify gas-intensive operations
  • Security Analysis - Trace fund flows and state changes
  • Failure Investigation - Understand why transactions reverted

Parameters​

  1. Transaction Hash - DATA, 32 Bytes

    • Hash of the transaction to trace
  2. Tracer Configuration - Object

    • tracer - Type of tracer ("callTracer", "prestateTracer", or custom)
    • tracerConfig - Additional configuration options
      • onlyTopCall - Return only top-level call
      • timeout - Execution timeout (e.g., "5s")
      • disableStorage - Disable storage capture
      • disableMemory - Disable memory capture
      • disableStack - Disable stack capture
{
"jsonrpc": "2.0",
"method": "debug_traceTransaction",
"params": [
"0x12345...",
{
"tracer": "callTracer",
"tracerConfig": {
"onlyTopCall": false
}
}
],
"id": 1
}

Returns​

Detailed trace object containing:

  • type - Call type (CALL, CREATE, DELEGATECALL, etc.)
  • from - Sender address
  • to - Recipient address
  • value - Wei transferred
  • gas - Gas provided
  • gasUsed - Gas consumed
  • input - Input data
  • output - Return data
  • error - Error message if failed
  • calls - Array of sub-calls
  • revertReason - Decoded revert reason

Implementation Examples​

import { JsonRpcProvider } from 'ethers';

const provider = new JsonRpcProvider('https://api-base-mainnet.n.dwellir.com/YOUR_API_KEY');

// Advanced transaction tracer
class TransactionDebugger {
constructor(provider) {
this.provider = provider;
}

async traceTransaction(txHash, options = {}) {
const defaultOptions = {
tracer: 'callTracer',
tracerConfig: {
onlyTopCall: false
}
};

const tracerConfig = { ...defaultOptions, ...options };

try {
const trace = await this.provider.send('debug_traceTransaction', [
txHash,
tracerConfig
]);

return this.parseTrace(trace);
} catch (error) {
throw new Error(`Failed to trace transaction: ${error.message}`);
}
}

parseTrace(trace) {
const result = {
mainCall: {
type: trace.type,
from: trace.from,
to: trace.to,
value: trace.value || '0x0',
gas: parseInt(trace.gas, 16),
gasUsed: parseInt(trace.gasUsed, 16),
success: !trace.error,
error: trace.error,
input: trace.input,
output: trace.output
},
subCalls: [],
totalGasUsed: parseInt(trace.gasUsed, 16),
callDepth: 0
};

if (trace.calls && trace.calls.length > 0) {
result.subCalls = this.flattenCalls(trace.calls);
result.callDepth = this.getMaxDepth(trace.calls);
}

return result;
}

flattenCalls(calls, depth = 1, result = []) {
for (const call of calls) {
result.push({
depth: depth,
type: call.type,
from: call.from,
to: call.to,
value: call.value || '0x0',
gas: parseInt(call.gas, 16),
gasUsed: parseInt(call.gasUsed, 16),
success: !call.error,
error: call.error,
input: call.input,
output: call.output
});

if (call.calls && call.calls.length > 0) {
this.flattenCalls(call.calls, depth + 1, result);
}
}
return result;
}

getMaxDepth(calls, currentDepth = 1) {
let maxDepth = currentDepth;

for (const call of calls) {
if (call.calls && call.calls.length > 0) {
const childDepth = this.getMaxDepth(call.calls, currentDepth + 1);
maxDepth = Math.max(maxDepth, childDepth);
}
}

return maxDepth;
}

async analyzeGasUsage(txHash) {
const trace = await this.traceTransaction(txHash);
const gasAnalysis = {
totalGas: trace.totalGasUsed,
mainCallGas: trace.mainCall.gasUsed,
subCallsGas: 0,
gasPerCall: [],
mostExpensiveCalls: []
};

// Analyze sub-calls
for (const call of trace.subCalls) {
gasAnalysis.subCallsGas += call.gasUsed;
gasAnalysis.gasPerCall.push({
to: call.to,
type: call.type,
gas: call.gasUsed,
percentage: ((call.gasUsed / trace.totalGasUsed) * 100).toFixed(2)
});
}

// Sort by gas usage
gasAnalysis.mostExpensiveCalls = gasAnalysis.gasPerCall
.sort((a, b) => b.gas - a.gas)
.slice(0, 5);

return gasAnalysis;
}

async traceWithPrestate(txHash) {
const trace = await this.provider.send('debug_traceTransaction', [
txHash,
{
tracer: 'prestateTracer',
tracerConfig: {
diffMode: true
}
}
]);

return this.analyzePrestateChanges(trace);
}

analyzePrestateChanges(trace) {
const changes = {
accountsModified: [],
storageChanges: [],
balanceChanges: []
};

// Analyze pre-state
if (trace.pre) {
for (const [address, state] of Object.entries(trace.pre)) {
changes.accountsModified.push(address);

if (state.balance) {
changes.balanceChanges.push({
address: address,
preBal: state.balance,
postBal: trace.post?.[address]?.balance || state.balance
});
}

if (state.storage) {
for (const [slot, value] of Object.entries(state.storage)) {
changes.storageChanges.push({
address: address,
slot: slot,
preValue: value,
postValue: trace.post?.[address]?.storage?.[slot] || value
});
}
}
}
}

return changes;
}

async traceContractCreation(txHash) {
const trace = await this.traceTransaction(txHash, {
tracer: 'callTracer',
tracerConfig: {
withLog: true
}
});

if (trace.mainCall.type !== 'CREATE' && trace.mainCall.type !== 'CREATE2') {
throw new Error('Transaction is not a contract creation');
}

return {
deployedAddress: trace.mainCall.to,
deploymentCost: trace.mainCall.gasUsed,
bytecode: trace.mainCall.input,
constructorReturn: trace.mainCall.output,
success: trace.mainCall.success,
subContracts: trace.subCalls
.filter(call => call.type === 'CREATE' || call.type === 'CREATE2')
.map(call => ({
address: call.to,
gasUsed: call.gasUsed
}))
};
}

async findRevertReason(txHash) {
const trace = await this.traceTransaction(txHash);

// Check main call
if (trace.mainCall.error) {
const reason = this.decodeRevertReason(trace.mainCall.output);
return {
level: 'main',
error: trace.mainCall.error,
decodedReason: reason,
callData: trace.mainCall.input
};
}

// Check sub-calls
for (const call of trace.subCalls) {
if (call.error) {
const reason = this.decodeRevertReason(call.output);
return {
level: `depth_${call.depth}`,
to: call.to,
error: call.error,
decodedReason: reason,
callData: call.input
};
}
}

return null;
}

decodeRevertReason(output) {
if (!output || output === '0x') return 'No revert message';

// Check for standard revert string (0x08c379a0)
if (output.startsWith('0x08c379a0')) {
try {
const reason = ethers.AbiCoder.defaultAbiCoder().decode(
['string'],
'0x' + output.slice(10)
)[0];
return reason;
} catch {
return 'Unable to decode revert reason';
}
}

// Check for panic code (0x4e487b71)
if (output.startsWith('0x4e487b71')) {
const panicCodes = {
'0x00': 'Generic compiler panic',
'0x01': 'Assert failed',
'0x11': 'Arithmetic overflow/underflow',
'0x12': 'Division by zero',
'0x21': 'Invalid enum value',
'0x22': 'Storage byte array error',
'0x31': 'Pop empty array',
'0x32': 'Array out of bounds',
'0x41': 'Too much memory allocated',
'0x51': 'Internal function call error'
};

const code = '0x' + output.slice(10, 12);
return panicCodes[code] || `Panic code: ${code}`;
}

return 'Custom error or unknown revert';
}
}

// Usage examples
const debugger = new TransactionDebugger(provider);

// Basic trace
const trace = await debugger.traceTransaction('0xTxHash');
console.log('Main call gas:', trace.mainCall.gasUsed);
console.log('Total sub-calls:', trace.subCalls.length);

// Gas analysis
const gasAnalysis = await debugger.analyzeGasUsage('0xTxHash');
console.log('Most expensive calls:', gasAnalysis.mostExpensiveCalls);

// Find revert reason
const revertInfo = await debugger.findRevertReason('0xFailedTxHash');
if (revertInfo) {
console.log('Revert reason:', revertInfo.decodedReason);
}

// Trace contract deployment
const deployment = await debugger.traceContractCreation('0xDeployTxHash');
console.log('Deployed at:', deployment.deployedAddress);

Common Use Cases​

1. DeFi Transaction Analysis​

// Analyze complex DeFi transactions
async function analyzeDeFiTransaction(txHash) {
const debugger = new TransactionDebugger(provider);
const trace = await debugger.traceTransaction(txHash);

// Identify DeFi operations
const operations = [];

for (const call of trace.subCalls) {
// Detect swap
if (call.input.startsWith('0x38ed1739')) { // swapExactTokensForTokens
operations.push({
type: 'SWAP',
protocol: call.to,
depth: call.depth,
gasUsed: call.gasUsed
});
}

// Detect liquidity operations
if (call.input.startsWith('0xe8e33700')) { // addLiquidity
operations.push({
type: 'ADD_LIQUIDITY',
protocol: call.to,
depth: call.depth,
gasUsed: call.gasUsed
});
}

// Detect lending operations
if (call.input.startsWith('0xa415bcad')) { // deposit
operations.push({
type: 'DEPOSIT',
protocol: call.to,
depth: call.depth,
gasUsed: call.gasUsed
});
}
}

return {
operations: operations,
totalOperations: operations.length,
protocolsUsed: [...new Set(operations.map(op => op.protocol))],
gasBreakdown: operations.map(op => ({
type: op.type,
gas: op.gasUsed,
percentage: ((op.gasUsed / trace.totalGasUsed) * 100).toFixed(2)
}))
};
}

2. Smart Contract Security Analysis​

// Security-focused transaction analysis
async function securityAnalysis(txHash) {
const debugger = new TransactionDebugger(provider);
const trace = await debugger.traceTransaction(txHash);

const securityFlags = {
reentrancy: false,
unexpectedETHTransfer: false,
failedCalls: [],
delegateCalls: [],
selfDestructs: [],
storageCollisions: []
};

// Check for reentrancy patterns
const callCounts = {};
for (const call of trace.subCalls) {
const key = `${call.from}-${call.to}`;
callCounts[key] = (callCounts[key] || 0) + 1;
if (callCounts[key] > 1) {
securityFlags.reentrancy = true;
}

// Track delegate calls
if (call.type === 'DELEGATECALL') {
securityFlags.delegateCalls.push({
from: call.from,
to: call.to,
depth: call.depth
});
}

// Track self-destructs
if (call.type === 'SELFDESTRUCT') {
securityFlags.selfDestructs.push(call.from);
}

// Track failed calls
if (!call.success) {
securityFlags.failedCalls.push({
to: call.to,
error: call.error,
depth: call.depth
});
}
}

return securityFlags;
}

3. Gas Optimization Recommendations​

// Generate gas optimization recommendations
async function getOptimizationRecommendations(txHash) {
const debugger = new TransactionDebugger(provider);
const gasAnalysis = await debugger.analyzeGasUsage(txHash);

const recommendations = [];

// Check for expensive patterns
for (const call of gasAnalysis.mostExpensiveCalls) {
if (call.percentage > 30) {
recommendations.push({
severity: 'high',
target: call.to,
message: `Call uses ${call.percentage}% of total gas`,
suggestion: 'Consider optimizing or caching results'
});
}

// SLOAD operations (storage reads)
if (call.gas > 2100 && call.gas < 2200) {
recommendations.push({
severity: 'medium',
target: call.to,
message: 'Multiple storage reads detected',
suggestion: 'Cache storage values in memory'
});
}
}

// Check call depth
const trace = await debugger.traceTransaction(txHash);
if (trace.callDepth > 3) {
recommendations.push({
severity: 'medium',
message: `Deep call stack (depth: ${trace.callDepth})`,
suggestion: 'Consider flattening contract architecture'
});
}

return recommendations;
}

Error Handling​

Error TypeDescriptionSolution
Method not availableDebug methods not enabledContact support for access
TimeoutTrace execution timeoutIncrease timeout in config
Out of memoryLarge trace dataUse onlyTopCall option
async function safeTrace(txHash, options = {}) {
try {
// Start with minimal trace
let trace = await provider.send('debug_traceTransaction', [
txHash,
{
tracer: 'callTracer',
tracerConfig: {
onlyTopCall: true,
timeout: '10s'
}
}
]);

// If successful, try full trace
if (options.fullTrace) {
trace = await provider.send('debug_traceTransaction', [
txHash,
{
tracer: 'callTracer',
tracerConfig: {
onlyTopCall: false,
timeout: '30s'
}
}
]);
}

return trace;

} catch (error) {
if (error.message.includes('timeout')) {
console.error('Trace timeout - transaction too complex');
// Return partial results
return {
error: 'timeout',
partial: true
};
}

if (error.message.includes('not available')) {
console.error('Debug methods not available on this endpoint');
return {
error: 'not_available',
suggestion: 'Contact support for debug method access'
};
}

throw error;
}
}

Need help? Contact our support team for debug method access or check the Base documentation.