Docs
Docs
Home
Getting Started
Agent Tooling
CLI
Supported Chains
Acala
Aptos
Arbitrum
Arc
Asset Hub
Astar
Avalanche
Base
Berachain
Bifrost
Binance Smart Chain
Bittensor
Blast
Boba Network
Bridge Hub
Celo
Centrifuge
Chiliz
Cronos
Enjin
Ethereum
Etherlink
Flow EVM Gateway
Gnosis
Hydration
Hyperliquid
Immutable
Ink
IoTeX
Kusama
Linea
LISK
Manta Atlantic
Manta Pacific
Mantle
MegaETH
Monad
Movement
Neuroweb
opBNB
Optimism
Plasma
Polkadot
Polygon
Pulsechain
Robinhood
Ronin
Scroll
Sonic
Starknet
Taiko
Tempo
Sui
TRON
Unichain
Viction
World Chain
XDC Network
XX Network
Zetachain
zkSync Era
Zora
Supported ChainsTempoJSON-RPC APIDebug Methods

debug_traceCall - Tempo RPC Method

Simulate a call on Tempo with debug_traceCall and get opcode, callTracer or prestateTracer output at any block, without sending a transaction.

Tempo RPC endpoint: endpoint URLs, plans and pricing.

Traces a call on Tempo without creating a transaction on-chain. This is a dry-run trace - it executes the call in the EVM at a specified block and returns detailed execution traces including opcodes, internal calls, and state changes, without any on-chain side effects.

Why Tempo? Build on a payments-first EVM chain with deterministic settlement and stablecoin-native fees with no native gas token, fees denominated in supported USD stablecoins, and full EVM RPC compatibility.

Archive Node Required

This method requires an archive node with debug APIs enabled when tracing against historical blocks. For "latest" or "pending" blocks, a full node with debug APIs may suffice. Dwellir provides archive node access for Tempo - ensure your plan includes debug namespace support.

When to Use This Method

debug_traceCall is powerful for payment application developers, wallet teams, stablecoin issuers, and treasury automation builders:

  • Simulating Transactions Before Sending - Preview the full execution trace of a transaction before committing it on-chain, catching reverts and unexpected behavior before spending gas on Tempo
  • Debugging Contract Interactions - Step through contract execution at the opcode level to understand complex interactions, delegate calls, and proxy patterns for merchant settlement, treasury operations, payout automation, and stablecoin-native financial applications
  • Gas Estimation With Trace Details - Go beyond eth_estimateGas by seeing exactly which opcodes and internal calls consume gas, enabling targeted optimization
  • Security Analysis - Analyze how a contract would execute a specific call, detecting reentrancy, unexpected state modifications, and access control issues

Best Practices

  • Requires archive node access when tracing against historical blocks
  • Use the stateDiff tracer for storage change analysis on simulated calls
  • The prestateTracer shows account state before the call executes
  • The callTracer is fastest for understanding call structure
JSON-RPC request
{
  "jsonrpc": "2.0",
  "method": "debug_traceCall",
  "params": [
    {
      "from": "0x1234567890abcdef1234567890abcdef12345678",
      "to": "0x0bd34b0a5be345c9bf7a147eb698e993511180cb",
      "data": "0x70a082310000000000000000000000000bd34b0a5be345c9bf7a147eb698e993511180cb"
    },
    "latest",
    {
      "tracer": "callTracer"
    }
  ],
  "id": 1
}

Code Examples

Common Use Cases

1. Pre-Flight Transaction Simulation

Test a transaction before sending it on Tempo to catch reverts and estimate costs:

JavaScript
async function simulateTransaction(provider, txParams) {
  // Use callTracer to see the full call tree
  const trace = await provider.send('debug_traceCall', [
    {
      from: txParams.from,
      to: txParams.to,
      data: txParams.data,
      value: txParams.value || '0x0',
      gas: txParams.gasLimit || '0x1e8480' // 2M gas default
    },
    'latest',
    { tracer: 'callTracer' }
  ]);

  const gasUsed = parseInt(trace.gasUsed, 16);

  if (trace.error) {
    console.error('Transaction would revert!');
    console.error(`  Error: ${trace.error}`);
    console.error(`  Reason: ${trace.revertReason || 'unknown'}`);
    console.error(`  Gas wasted: ${gasUsed}`);
    return { success: false, error: trace.error, revertReason: trace.revertReason, gasUsed };
  }

  // Analyze internal calls for unexpected behavior
  const allCalls = flattenCalls(trace);
  const delegateCalls = allCalls.filter(c => c.type === 'DELEGATECALL');
  const creates = allCalls.filter(c => c.type === 'CREATE' || c.type === 'CREATE2');

  console.log('Simulation results:');
  console.log(`  Gas used: ${gasUsed}`);
  console.log(`  Internal calls: ${allCalls.length}`);
  console.log(`  Delegate calls: ${delegateCalls.length}`);
  console.log(`  Contract creations: ${creates.length}`);

  return { success: true, gasUsed, trace };
}

function flattenCalls(trace) {
  const calls = [trace];
  for (const sub of trace.calls || []) {
    calls.push(...flattenCalls(sub));
  }
  return calls;
}

2. Gas Optimization Analysis

Identify the most expensive opcodes in a contract call on Tempo:

JavaScript
async function analyzeGasHotspots(provider, callObj) {
  // Use default opcode tracer for step-by-step gas analysis
  const trace = await provider.send('debug_traceCall', [
    callObj,
    'latest',
    { disableStorage: false, enableReturnData: true }
  ]);

  const opcodeGas = {};

  for (const log of trace.structLogs) {
    if (!opcodeGas[log.op]) {
      opcodeGas[log.op] = { count: 0, totalGas: 0 };
    }
    opcodeGas[log.op].count++;
    opcodeGas[log.op].totalGas += log.gasCost;
  }

  // Sort by total gas cost
  const sorted = Object.entries(opcodeGas)
    .map(([op, stats]) => ({ op, ...stats, avgGas: Math.round(stats.totalGas / stats.count) }))
    .sort((a, b) => b.totalGas - a.totalGas);

  console.log('Gas hotspots:');
  console.log('Op'.padEnd(15), 'Count'.padStart(8), 'Total Gas'.padStart(12), 'Avg Gas'.padStart(10));
  for (const entry of sorted.slice(0, 10)) {
    console.log(
      entry.op.padEnd(15),
      String(entry.count).padStart(8),
      String(entry.totalGas).padStart(12),
      String(entry.avgGas).padStart(10)
    );
  }

  // Identify SSTORE/SLOAD hotspots (most expensive storage operations)
  const storageOps = trace.structLogs.filter(
    log => log.op === 'SSTORE' || log.op === 'SLOAD'
  );
  console.log(`\nStorage operations: ${storageOps.length} (${storageOps.filter(s => s.op === 'SSTORE').length} writes)`);

  return { opcodeGas: sorted, totalSteps: trace.structLogs.length, totalGas: trace.gas };
}

3. Security Analysis of Contract Interactions

Detect potentially dangerous patterns when calling a contract on Tempo:

Python
import requests

def security_trace_call(call_object, block='latest'):
    response = requests.post('https://api-tempo-mainnet.n.dwellir.com/YOUR_API_KEY', json={
        'jsonrpc': '2.0',
        'method': 'debug_traceCall',
        'params': [call_object, block, {'tracer': 'callTracer'}],
        'id': 1
    })
    trace = response.json()['result']

    warnings = []
    all_calls = flatten_calls(trace)

    for call in all_calls:
        # Detect unexpected delegate calls
        if call['type'] == 'DELEGATECALL':
            warnings.append(f'DELEGATECALL to {call["to"]} - could modify caller storage')

        # Detect value transfers to unexpected addresses
        value = int(call.get('value', '0x0'), 16)
        if value > 0 and call['to'] != call_object.get('to', '').lower():
            warnings.append(
                f'Value transfer of {value} wei to unexpected address {call["to"]}'
            )

        # Detect selfdestruct (CALL with no input to EOA after value)
        if call.get('error'):
            warnings.append(f'Internal revert at {call["to"]}: {call["error"]}')

    if trace.get('error'):
        print(f'TOP-LEVEL REVERT: {trace["error"]}')
        if trace.get('revertReason'):
            print(f'  Reason: {trace["revertReason"]}')
    else:
        gas_used = int(trace['gasUsed'], 16)
        print(f'Call succeeded: {gas_used} gas used')

    if warnings:
        print(f'\nSecurity warnings ({len(warnings)}):')
        for w in warnings:
            print(f'  - {w}')
    else:
        print('No security warnings detected')

    return {'success': not trace.get('error'), 'warnings': warnings}

def flatten_calls(trace):
    calls = [trace]
    for sub in trace.get('calls', []):
        calls.extend(flatten_calls(sub))
    return calls

# Example: analyze a token approval
security_trace_call({
    'from': '0x1234567890abcdef1234567890abcdef12345678',
    'to': '0x0bd34b0a5be345c9bf7a147eb698e993511180cb',
    'data': '0x095ea7b3000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
})

Error Handling

Common errors and solutions:

Error CodeDescriptionSolution
-32000Execution revertedThe call would revert - check the revertReason in the trace result for details
-32000Missing trie nodeHistorical block state not available - use an archive node endpoint
-32601Method not foundDebug namespace is not enabled on the node
-32603Internal errorCall may be too complex - try increasing timeout or use disableStorage
-32005Rate limit exceededReduce request frequency or upgrade your plan
-32602Invalid paramsVerify the call object fields and block number format
-32000Insufficient fundsThe from address lacks the balance for the value field - note this is simulated, not real funds
JavaScript
async function safeTraceCall(provider, callObj, blockNumber = 'latest', options = {}) {
  const defaultOptions = {
    tracer: 'callTracer',
    timeout: '30s',
    ...options
  };

  try {
    return await provider.send('debug_traceCall', [callObj, blockNumber, defaultOptions]);
  } catch (error) {
    if (error.code === -32601) {
      throw new Error('debug_traceCall not available - ensure archive node with debug API');
    }
    if (error.code === -32000 && error.message?.includes('trie node')) {
      throw new Error(`State unavailable for block ${blockNumber} - use an archive endpoint`);
    }
    if (error.message?.includes('timeout')) {
      console.warn('Trace timed out - retrying with reduced detail');
      return await provider.send('debug_traceCall', [
        callObj,
        blockNumber,
        { tracer: 'callTracer', tracerConfig: { onlyTopCall: true } }
      ]);
    }
    throw error;
  }
}

Related Methods

  • eth_call - Execute a call without trace (returns only the result, not execution details)
  • debug_traceTransaction - Trace an already-executed transaction by hash
  • eth_estimateGas - Estimate gas for a call (without trace details)
  • debug_traceBlockByNumber - Trace all transactions in a block by number
  • debug_traceBlockByHash - Trace all transactions in a block by hash

debug_traceBlockByNumber

Trace all transactions in a block by number on Tempo. Analyze gas consumption, debug state transitions, and inspect historical block execution.

debug_traceTransaction

Trace a transaction execution on Tempo. Requires archive node for debugging merchant settlement, treasury operations, payout automation, and stablecoin-native financial applications.

On this page

When to Use This MethodBest PracticesCode ExamplesCommon Use Cases1. Pre-Flight Transaction Simulation2. Gas Optimization Analysis3. Security Analysis of Contract InteractionsError HandlingRelated Methods