Docs
Supported ChainsMantleJSON-RPC APIDebug Methods

debug_traceCall - Mantle RPC Method

Trace a call without creating a transaction on Mantle. Simulate transactions, debug contract interactions, and analyze gas usage with full execution traces.

Traces a call on Mantle 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 Mantle? Build on the world's largest ZK rollup by TVL with $2.5B+ secured and deep Bybit integration with near-instant ZK finality via OP Succinct, $6.2B treasury backing, mETH liquid staking, and 25% Bybit trading fee discounts.

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

When to Use This Method

debug_traceCall is powerful for DeFi developers, liquid staking builders, and teams seeking institutional exchange integration:

  • 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 Mantle
  • Debugging Contract Interactions — Step through contract execution at the opcode level to understand complex interactions, delegate calls, and proxy patterns for liquid staking (mETH $1.87B TVL), institutional DeFi via Bybit, and yield optimization strategies
  • 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

Request Parameters

Request
callObjectObject

Transaction call object (same format as eth_call)

blockNumberQUANTITY|TAG

Block number as hex string, or tag: "earliest", "latest", "pending"

tracerConfigObject

Tracer configuration object (see options below)

fromDATA

Sender address (defaults to zero address)

toDATA

Recipient / contract address

gasQUANTITY

Gas limit for the call

gasPriceQUANTITY

Gas price in wei

maxFeePerGasQUANTITY

Max fee per gas (EIP-1559)

maxPriorityFeePerGasQUANTITY

Max priority fee per gas (EIP-1559)

valueQUANTITY

Value to send in wei

dataDATA

Encoded function call data

Response Body

Response
typestring

Call type (CALL, DELEGATECALL, STATICCALL, CREATE, CREATE2)

fromDATA

Sender address

toDATA

Recipient address

valueQUANTITY

Value transferred in wei

gasQUANTITY

Gas provided

gasUsedQUANTITY

Gas consumed

inputDATA

Call data

outputDATA

Return data

errorstring

Error message if the call reverted

revertReasonstring

Decoded revert reason (if available)

callsArray

Sub-calls made during execution

gasQUANTITY

Gas provided

returnValueDATA

Return value of the call

structLogsArray

Array of opcode execution steps

structLogs[].pcQUANTITY

Program counter

structLogs[].opstring

Opcode name

structLogs[].gasQUANTITY

Remaining gas

structLogs[].gasCostQUANTITY

Gas cost of this opcode

structLogs[].depthQUANTITY

Call depth

structLogs[].stackArray<DATA>

Stack contents (if not disabled)

structLogs[].storageObject

Storage changes (if not disabled)

addressObject

State of each account touched by the call

address.balanceQUANTITY

Account balance

address.nonceQUANTITY

Account nonce

address.codeDATA

Contract bytecode (if contract account)

address.storageObject

Storage slots accessed

Error Responses

Errors
Error Response (Reverted Call)

Code Examples

Bash
# Trace a read-only contract call with call tracer
curl -X POST https://api-mantle-mainnet.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "debug_traceCall",
    "params": [
      {
        "to": "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8",
        "data": "0x70a0823100000000000000000000000078c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8"
      },
      "latest",
      {"tracer": "callTracer"}
    ],
    "id": 1
  }'

# Trace with default opcode tracer
curl -X POST https://api-mantle-mainnet.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "debug_traceCall",
    "params": [
      {
        "to": "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8",
        "data": "0x70a0823100000000000000000000000078c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8"
      },
      "latest",
      {"disableStorage": true, "enableReturnData": true}
    ],
    "id": 1
  }'

Common Use Cases

1. Pre-Flight Transaction Simulation

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

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

Python
import requests

def security_trace_call(call_object, block='latest'):
    response = requests.post('https://api-mantle-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': '0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8',
    '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;
  }
}