Skip to main content

debug_traceCall

Executes a call and returns detailed trace information without creating an actual transaction on the blockchain. This method is invaluable for testing contract interactions, debugging issues, and estimating gas consumption without spending any gas.

Premium Method

This method is available on premium plans with debug API access. Contact our team for access.

When to Use This Method​

debug_traceCall is essential for:

  • Contract Testing - Test contract methods without spending gas
  • Debugging Failed Transactions - Understand why a transaction would fail
  • Gas Optimization - Analyze gas consumption before executing
  • Security Analysis - Trace potential attack vectors safely
  • Integration Testing - Validate contract interactions before deployment
  • What-If Analysis - Simulate different scenarios without commitment

Parameters​

1. transaction (required)​

Transaction call object with the following fields:

FieldTypeRequiredDescription
fromAddressNoThe address the transaction is sent from
toAddressYesThe address the transaction is directed to
gasQuantityNoGas provided for the transaction execution
gasPriceQuantityNoGas price for each paid gas
valueQuantityNoValue sent with this transaction
dataDataNoMethod signature and encoded parameters

2. blockNumber (required)​

  • Type: QUANTITY|TAG
  • Description: Block number or tag ("latest", "earliest", "pending")
  • Example: "latest" or "0x5bad55"

3. tracer (optional)​

  • Type: Object
  • Description: Tracer configuration object
  • Fields:
    • tracer: The type of tracer (callTracer, prestateTracer, or custom)
    • tracerConfig: Additional configuration options

Returns​

Trace object containing:

FieldTypeDescription
failedBooleanWhether the call execution failed
gasQuantityGas consumed by the execution
returnValueDataReturn data from the call
structLogsArrayDetailed execution steps (if using default tracer)
fromAddressSender address (with callTracer)
toAddressRecipient address (with callTracer)
typeStringCall type (CALL, CREATE, etc.)
inputDataCall input data
outputDataCall output data
errorStringError message if execution failed
revertReasonStringDecoded revert reason if available
callsArrayNested calls (with callTracer)

Implementation Examples​

curl -X POST https://api-arbitrum-mainnet.n.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_traceCall",
"params": [
{
"from": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5",
"to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"gas": "0x30d40",
"gasPrice": "0x3b9aca00",
"value": "0x0",
"data": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb5"
},
"latest",
{
"tracer": "callTracer",
"tracerConfig": {
"withLog": true
}
}
],
"id": 1
}'

Response Example​

Successful Response​

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"from": "0x742d35cc6634c0532925a3b844bc9e7595f0beb5",
"to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"gas": "0x30d40",
"gasUsed": "0x6d60",
"input": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb5",
"output": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"type": "CALL",
"value": "0x0",
"calls": [
{
"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"to": "0x4200000000000000000000000000000000000001",
"gas": "0x2a8d8",
"gasUsed": "0x2108",
"input": "0x",
"output": "0x",
"type": "STATICCALL",
"value": "0x0"
}
]
}
}

Common Use Cases​

1. Pre-Transaction Validation​

Validate transactions before sending:

async function validateTransaction(txParams) {
try {
const trace = await traceCall(txParams, 'latest');

if (trace.error) {
console.log('Transaction will fail:', trace.error);

// Decode revert reason
if (trace.revertReason) {
console.log('Reason:', trace.revertReason);
}

return {
valid: false,
reason: trace.error,
gasEstimate: null
};
}

const gasUsed = parseInt(trace.gasUsed, 16);
const safeGasLimit = Math.ceil(gasUsed * 1.2); // 20% buffer

return {
valid: true,
gasEstimate: safeGasLimit,
output: trace.output
};
} catch (error) {
return {
valid: false,
reason: error.message,
gasEstimate: null
};
}
}

2. Smart Contract Testing​

Test contract methods without deployment:

def test_contract_method(contract_address, method_name, params, caller='0x0000000000000000000000000000000000000000'):
"""
Test a contract method without executing
"""
# Encode the method call
from eth_abi import encode_abi
from eth_utils import function_signature_to_4byte_selector

# Get method signature
method_sig = function_signature_to_4byte_selector(method_name)

# Encode parameters
encoded_params = encode_abi(params['types'], params['values'])

call_data = '0x' + method_sig.hex() + encoded_params.hex()

tx_params = {
'from': caller,
'to': contract_address,
'data': call_data,
'gas': hex(1000000)
}

trace = trace_call(tx_params)

# Analyze the result
if trace.get('error'):
return {
'success': False,
'error': trace['error'],
'gas_used': 0
}

return {
'success': True,
'output': trace.get('output'),
'gas_used': int(trace.get('gasUsed', '0x0'), 16),
'internal_calls': len(trace.get('calls', []))
}

# Example: Test a swap function
result = test_contract_method(
contract_address='0x1234...',
method_name='swap(address,address,uint256)',
params={
'types': ['address', 'address', 'uint256'],
'values': [token_a, token_b, amount]
}
)

3. Gas Optimization Analysis​

Compare gas usage of different approaches:

async function compareGasUsage(implementations) {
const results = [];

for (const impl of implementations) {
const trace = await traceCall({
from: impl.from,
to: impl.contract,
data: impl.calldata,
gas: '0xf4240' // 1M gas
}, 'latest');

results.push({
name: impl.name,
gasUsed: parseInt(trace.gasUsed, 16),
success: !trace.error,
callDepth: countCallDepth(trace),
storageAccess: countStorageOps(trace)
});
}

// Sort by gas efficiency
results.sort((a, b) => a.gasUsed - b.gasUsed);

console.log('Gas Usage Comparison:');
results.forEach((r, i) => {
console.log(`${i + 1}. ${r.name}: ${r.gasUsed.toLocaleString()} gas`);
console.log(` Call depth: ${r.callDepth}, Storage ops: ${r.storageAccess}`);
});

return results;
}

function countCallDepth(trace, depth = 0) {
if (!trace.calls || trace.calls.length === 0) return depth;

let maxDepth = depth;
for (const call of trace.calls) {
const callDepth = countCallDepth(call, depth + 1);
maxDepth = Math.max(maxDepth, callDepth);
}

return maxDepth;
}

4. Security Analysis​

Check for reentrancy and other issues:

def analyze_reentrancy_risk(contract_address, method_data):
"""
Analyze potential reentrancy vulnerabilities
"""
trace = trace_call({
'to': contract_address,
'data': method_data,
'gas': hex(5000000)
})

# Look for external calls before state changes
external_calls = []
state_changes = []

def analyze_calls(call_trace, depth=0):
if call_trace.get('type') == 'CALL':
external_calls.append({
'depth': depth,
'to': call_trace.get('to'),
'value': call_trace.get('value', '0x0')
})

if call_trace.get('type') == 'SSTORE':
state_changes.append({
'depth': depth,
'location': call_trace.get('to')
})

for subcall in call_trace.get('calls', []):
analyze_calls(subcall, depth + 1)

analyze_calls(trace)

# Check for external calls before state changes
risky_patterns = []
for ext_call in external_calls:
for state_change in state_changes:
if ext_call['depth'] < state_change['depth']:
risky_patterns.append({
'external_call': ext_call,
'state_change': state_change,
'risk': 'Potential reentrancy'
})

return {
'external_calls': len(external_calls),
'state_changes': len(state_changes),
'risky_patterns': risky_patterns
}

Error Handling​

Common errors and solutions:

Error CodeDescriptionSolution
-32602Invalid paramsCheck transaction object format
-32000Execution revertedTransaction would fail; check revert reason
-32603Internal errorReduce gas limit or simplify call
3Execution revertedEVM reverted the execution
-32005TimeoutCall is too complex; optimize or increase timeout

Error Handling Example​

async function safeTraceCall(params, block = 'latest') {
try {
const trace = await traceCall(params, block);

if (trace.error) {
// Parse common error patterns
if (trace.error.includes('insufficient funds')) {
throw new Error('Account has insufficient balance');
}
if (trace.error.includes('gas required exceeds allowance')) {
throw new Error('Gas limit too low');
}
if (trace.revertReason) {
throw new Error(`Reverted: ${trace.revertReason}`);
}

throw new Error(trace.error);
}

return trace;
} catch (error) {
// Handle RPC errors
if (error.code === -32602) {
throw new Error('Invalid transaction parameters');
}
if (error.code === -32000) {
throw new Error('Transaction execution failed');
}

throw error;
}
}

Best Practices​

  1. Always Specify Gas Limit: Prevent infinite loops
  2. Use Appropriate Block: Test against the latest state
  3. Cache Results: Traces for past blocks are immutable
  4. Handle Reverts Gracefully: Parse and display revert reasons
  5. Validate Input Data: Ensure correct encoding before tracing

Need help? Contact our support team for assistance with debug methods.