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 ChainsPlasmaJSON-RPC APIDebug Methods

debug_traceCall - Plasma RPC Method

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

Plasma RPC endpoint: endpoint URLs, plans and pricing.

Traces a call on Plasma 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 Plasma? Build on an EVM Layer 1 for stablecoin payments with PlasmaBFT consensus and Reth execution with 1 second blocks, XPL gas, full EVM JSON-RPC, and debug plus txpool on Dwellir.

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 Plasma - ensure your plan includes debug namespace support.

When to Use This Method

debug_traceCall is powerful for payment app developers, wallet teams, and teams shipping Solidity contracts for dollar transfers:

  • 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 Plasma
  • Debugging Contract Interactions - Step through contract execution at the opcode level to understand complex interactions, delegate calls, and proxy patterns for stablecoin transfers, payment apps, and wallets that need 1 second confirmation
  • 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": "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
      "data": "0x70a08231000000000000000000000000B8CE59FC3717ada4C02eaDF9682A9e934F625ebb"
    },
    "latest",
    {
      "tracer": "callTracer"
    }
  ],
  "id": 1
}

Code Examples

Common Use Cases

1. Pre-Flight Transaction Simulation

Test a transaction before sending it on Plasma 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 Plasma:

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 Plasma:

Python
import requests

def security_trace_call(call_object, block='latest'):
    response = requests.post('https://api-plasma-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': '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb',
    '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 Plasma. Analyze gas consumption, debug state transitions, and inspect historical block execution.

debug_traceTransaction

Trace a transaction execution on Plasma. Requires archive node for debugging stablecoin transfers, payment apps, and wallets that need 1 second confirmation.

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