debug_traceCall - Zetachain RPC Method
Trace a call without creating a transaction on Zetachain. Simulate transactions, debug contract interactions, and analyze gas usage with full execution traces.
Traces a call on Zetachain 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 Zetachain? Build on the universal omnichain blockchain enabling cross-chain smart contracts across 50+ chains including Bitcoin with native Bitcoin support, 50+ chain interoperability via UNISON, no bridging required, and partnerships with Curve and SushiSwap.
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 Zetachain — ensure your plan includes debug namespace support.
When to Use This Method
debug_traceCall is powerful for cross-chain dApp developers, Bitcoin DeFi builders, and teams requiring native multi-chain interoperability:
- 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 Zetachain
- Debugging Contract Interactions — Step through contract execution at the opcode level to understand complex interactions, delegate calls, and proxy patterns for omnichain DeFi, native Bitcoin smart contracts, cross-chain asset management, and unified liquidity aggregation
- Gas Estimation With Trace Details — Go beyond
eth_estimateGasby 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
Transaction call object (same format as eth_call)
Block number as hex string, or tag: "earliest", "latest", "pending"
Tracer configuration object (see options below)
Sender address (defaults to zero address)
Recipient / contract address
Gas limit for the call
Gas price in wei
Max fee per gas (EIP-1559)
Max priority fee per gas (EIP-1559)
Value to send in wei
Encoded function call data
Response Body
Call type (CALL, DELEGATECALL, STATICCALL, CREATE, CREATE2)
Sender address
Recipient address
Value transferred in wei
Gas provided
Gas consumed
Call data
Return data
Error message if the call reverted
Decoded revert reason (if available)
Sub-calls made during execution
Gas provided
Return value of the call
Array of opcode execution steps
Program counter
Opcode name
Remaining gas
Gas cost of this opcode
Call depth
Stack contents (if not disabled)
Storage changes (if not disabled)
State of each account touched by the call
Account balance
Account nonce
Contract bytecode (if contract account)
Storage slots accessed
Error Responses
Code Examples
# Trace a read-only contract call with call tracer
curl -X POST https://api-zetachain-mainnet.n.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_traceCall",
"params": [
{
"to": "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf",
"data": "0x70a082310000000000000000000000005F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf"
},
"latest",
{"tracer": "callTracer"}
],
"id": 1
}'
# Trace with default opcode tracer
curl -X POST https://api-zetachain-mainnet.n.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_traceCall",
"params": [
{
"to": "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf",
"data": "0x70a082310000000000000000000000005F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf"
},
"latest",
{"disableStorage": true, "enableReturnData": true}
],
"id": 1
}'Common Use Cases
1. Pre-Flight Transaction Simulation
Test a transaction before sending it on Zetachain to catch reverts and estimate costs:
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 Zetachain:
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 Zetachain:
import requests
def security_trace_call(call_object, block='latest'):
response = requests.post('https://api-zetachain-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': '0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf',
'data': '0x095ea7b3000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
})Error Handling
Common errors and solutions:
| Error Code | Description | Solution |
|---|---|---|
| -32000 | Execution reverted | The call would revert — check the revertReason in the trace result for details |
| -32000 | Missing trie node | Historical block state not available — use an archive node endpoint |
| -32601 | Method not found | Debug namespace is not enabled on the node |
| -32603 | Internal error | Call may be too complex — try increasing timeout or use disableStorage |
| -32005 | Rate limit exceeded | Reduce request frequency or upgrade your plan |
| -32602 | Invalid params | Verify the call object fields and block number format |
| -32000 | Insufficient funds | The from address lacks the balance for the value field — note this is simulated, not real funds |
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 hasheth_estimateGas— Estimate gas for a call (without trace details)debug_traceBlockByNumber— Trace all transactions in a block by numberdebug_traceBlockByHash— Trace all transactions in a block by hash
debug_traceBlockByNumber
Trace all transactions in a block by number on Zetachain. Analyze gas consumption, debug state transitions, and inspect historical block execution.
debug_traceTransaction
Trace a transaction execution on Zetachain. Requires archive node for debugging omnichain DeFi, native Bitcoin smart contracts, cross-chain asset management, and unified liquidity aggregation.