Docs

debug_traceBlockByHash - Base RPC Method

Trace all transactions in a block by hash on Base. Investigate specific blocks, analyze execution order, and debug reverts with detailed traces.

Traces all transactions in a block on Base identified by its block hash. Returns detailed execution traces for every transaction, making it ideal for investigating specific blocks when you know the exact hash.

Why Base? Build on Coinbase's L2 with 54% of L2 market revenue and direct access to 110M+ Coinbase users with $8B+ TVL, $0.08 gas fees, built-in Coinbase distribution, and seamless fiat rails.

Archive Node Required

This method requires an archive node with debug APIs enabled. Standard full nodes prune the historical state needed for transaction tracing. Dwellir provides archive node access for Base — ensure your plan includes debug namespace support.

When to Use This Method

debug_traceBlockByHash is essential for consumer app developers, SocialFi builders, and teams seeking easy fiat onramps:

  • Investigating Specific Blocks — When you have a block hash from an event, alert, or on-chain reference, trace every transaction in that exact block on Base
  • Analyzing Transaction Execution Order — Understand how transactions within a block interact, including cross-transaction state dependencies
  • Debugging Reverted Transactions — Find the exact opcode where transactions failed across an entire block for consumer dApps, SocialFi, NFT marketplaces, and merchant payment integrations
  • Fork and Reorg Analysis — Use block hashes to trace transactions in specific forks, ensuring you analyze the correct chain branch

Code Examples

Common Use Cases

1. Find All Reverted Transactions in a Block

Identify and analyze failed transactions on Base:

JavaScript
async function findReverts(provider, blockHash) {
  const traces = await provider.send('debug_traceBlockByHash', [
    blockHash,
    { tracer: 'callTracer' }
  ]);

  const reverts = [];

  for (const trace of traces) {
    if (trace.result.error) {
      reverts.push({
        txHash: trace.txHash,
        error: trace.result.error,
        revertReason: trace.result.revertReason || 'N/A',
        from: trace.result.from,
        to: trace.result.to,
        gasUsed: parseInt(trace.result.gasUsed, 16)
      });
    }

    // Also check sub-calls for internal reverts
    const internalReverts = findInternalReverts(trace.result.calls || []);
    if (internalReverts.length > 0) {
      reverts.push({
        txHash: trace.txHash,
        internalReverts,
        topLevelSuccess: !trace.result.error
      });
    }
  }

  console.log(`Found ${reverts.length} reverted transactions out of ${traces.length}`);
  for (const r of reverts) {
    console.log(`  ${r.txHash}: ${r.error || 'internal revert'}`);
  }
  return reverts;
}

function findInternalReverts(calls) {
  const reverts = [];
  for (const call of calls) {
    if (call.error) {
      reverts.push({ type: call.type, to: call.to, error: call.error });
    }
    reverts.push(...findInternalReverts(call.calls || []));
  }
  return reverts;
}

2. Analyze Token Transfer Patterns in a Block

Extract all ERC-20 transfer events from block traces on Base:

Python
import requests

def analyze_token_transfers(block_hash):
    response = requests.post('https://api-base-mainnet-archive.n.dwellir.com/YOUR_API_KEY', json={
        'jsonrpc': '2.0',
        'method': 'debug_traceBlockByHash',
        'params': [block_hash, {'tracer': 'callTracer'}],
        'id': 1
    })
    traces = response.json()['result']

    # ERC-20 transfer(address,uint256) selector
    TRANSFER_SELECTOR = '0xa9059cbb'
    # ERC-20 transferFrom(address,address,uint256) selector
    TRANSFER_FROM_SELECTOR = '0x23b872dd'

    transfers = []

    for trace in traces:
        calls = flatten_calls(trace['result'])
        for call in calls:
            input_data = call.get('input', '')
            if input_data.startswith(TRANSFER_SELECTOR) or \
               input_data.startswith(TRANSFER_FROM_SELECTOR):
                transfers.append({
                    'tx_hash': trace['txHash'],
                    'token_contract': call['to'],
                    'from': call['from'],
                    'type': call['type'],
                    'gas_used': int(call.get('gasUsed', '0x0'), 16)
                })

    print(f'Found {len(transfers)} token transfers in block')
    # Group by token contract
    by_token = {}
    for t in transfers:
        by_token.setdefault(t['token_contract'], []).append(t)

    for token, txs in by_token.items():
        print(f'  {token}: {len(txs)} transfers')

    return transfers

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

analyze_token_transfers('0xf47c4f664172629e9f74da58f558c6f553e2662adc7a3cf0af0292347bd2f358')

3. Block Execution State Diff

Compare account states before and after block execution using the prestate tracer:

JavaScript
async function getBlockStateDiff(provider, blockHash) {
  // Get prestate — accounts state before each transaction
  const prestateTraces = await provider.send('debug_traceBlockByHash', [
    blockHash,
    { tracer: 'prestateTracer', tracerConfig: { diffMode: true } }
  ]);

  const allAddresses = new Set();
  const balanceChanges = {};

  for (const trace of prestateTraces) {
    const pre = trace.result.pre || trace.result;
    const post = trace.result.post || {};

    for (const [addr, state] of Object.entries(pre)) {
      allAddresses.add(addr);
      if (!balanceChanges[addr]) {
        balanceChanges[addr] = {
          preBal: BigInt(state.balance || '0x0'),
          postBal: BigInt((post[addr]?.balance) || state.balance || '0x0')
        };
      }
    }
  }

  console.log(`Block touched ${allAddresses.size} unique addresses`);
  for (const [addr, change] of Object.entries(balanceChanges)) {
    const diff = change.postBal - change.preBal;
    if (diff !== 0n) {
      console.log(`  ${addr}: ${diff > 0n ? '+' : ''}${diff} wei`);
    }
  }

  return balanceChanges;
}

Error Handling

Common errors and solutions:

Error CodeDescriptionSolution
-32000Block not foundVerify the block hash is correct and exists on Base
-32000Missing trie nodeNode is not an archive node or state has been pruned — use an archive endpoint
-32601Method not foundDebug namespace is not enabled on the node
-32603Internal errorBlock may be too large to trace — increase timeout or use disableStorage
-32005Rate limit exceededReduce request frequency or upgrade your plan
-32002Resource unavailableNode is syncing or the requested block is not yet available
JavaScript
async function safeTraceBlockByHash(provider, blockHash, options = {}) {
  const defaultOptions = {
    tracer: 'callTracer',
    timeout: '60s',
    ...options
  };

  try {
    return await provider.send('debug_traceBlockByHash', [blockHash, defaultOptions]);
  } catch (error) {
    if (error.code === -32601) {
      throw new Error('debug_traceBlockByHash not available — ensure archive node with debug API');
    }
    if (error.code === -32000 && error.message?.includes('not found')) {
      throw new Error(`Block ${blockHash} not found — verify the hash is correct`);
    }
    if (error.message?.includes('timeout')) {
      console.warn('Trace timed out — retrying with top-level calls only');
      return await provider.send('debug_traceBlockByHash', [
        blockHash,
        { tracer: 'callTracer', tracerConfig: { onlyTopCall: true } }
      ]);
    }
    throw error;
  }
}