Skip to main content

debug_traceBlock

Returns execution traces for all transactions in a block. Useful for analyzing block-wide patterns, MEV activities, and transaction interactions.

Premium Method

This method requires enhanced API access. Please contact support for access to debug methods.

When to Use This Method​

debug_traceBlock is essential for:

  • Block Analysis - Understand all transactions in a block
  • MEV Detection - Identify sandwich attacks and arbitrage
  • Gas Analysis - Compare gas usage across transactions
  • State Analysis - Track state changes throughout a block

Parameters​

  1. Block Identifier - DATA

    • RLP-encoded block or block hash
  2. Tracer Configuration - Object

    • tracer - Type of tracer to use
    • tracerConfig - Configuration options
      • onlyTopCall - Only trace top-level calls
      • timeout - Execution timeout per transaction
{
"jsonrpc": "2.0",
"method": "debug_traceBlock",
"params": [
"0xf90217...", // RLP-encoded block
{
"tracer": "callTracer",
"tracerConfig": {
"onlyTopCall": false
}
}
],
"id": 1
}

Returns​

Array of transaction traces, each containing:

  • Transaction trace data (same as debug_traceTransaction)
  • Transaction index in block
  • Transaction hash

Implementation Examples​

import { JsonRpcProvider } from 'ethers';

const provider = new JsonRpcProvider('https://api-base-mainnet.n.dwellir.com/YOUR_API_KEY');

// Block-wide transaction analyzer
class BlockAnalyzer {
constructor(provider) {
this.provider = provider;
}

async traceBlock(blockNumber, options = {}) {
try {
// Get block data
const block = await this.provider.getBlock(blockNumber);

if (!block) {
throw new Error('Block not found');
}

// Trace all transactions
const traces = await this.provider.send('debug_traceBlock', [
block.hash,
{
tracer: options.tracer || 'callTracer',
tracerConfig: options.tracerConfig || {
onlyTopCall: false,
timeout: '10s'
}
}
]);

return this.analyzeBlockTraces(traces, block);
} catch (error) {
throw new Error(`Failed to trace block: ${error.message}`);
}
}

analyzeBlockTraces(traces, block) {
const analysis = {
blockNumber: block.number,
blockHash: block.hash,
timestamp: block.timestamp,
transactionCount: traces.length,
totalGasUsed: 0,
transactions: [],
patterns: {
mev: [],
highGas: [],
failed: [],
contracts: new Set()
}
};

// Analyze each transaction
traces.forEach((trace, index) => {
const tx = block.transactions[index];
const gasUsed = parseInt(trace.result?.gasUsed || '0x0', 16);

analysis.totalGasUsed += gasUsed;

const txAnalysis = {
index: index,
hash: tx,
from: trace.result?.from,
to: trace.result?.to,
gasUsed: gasUsed,
success: !trace.result?.error,
callCount: this.countCalls(trace.result),
depth: this.getMaxDepth(trace.result)
};

analysis.transactions.push(txAnalysis);

// Detect patterns
if (gasUsed > 1000000) {
analysis.patterns.highGas.push(index);
}

if (trace.result?.error) {
analysis.patterns.failed.push(index);
}

if (trace.result?.to) {
analysis.patterns.contracts.add(trace.result.to);
}
});

// Detect MEV patterns
analysis.patterns.mev = this.detectMEVPatterns(traces);
analysis.patterns.contracts = Array.from(analysis.patterns.contracts);

return analysis;
}

countCalls(trace) {
if (!trace || !trace.calls) return 1;

let count = 1;
for (const call of trace.calls) {
count += this.countCalls(call);
}
return count;
}

getMaxDepth(trace, currentDepth = 0) {
if (!trace || !trace.calls || trace.calls.length === 0) {
return currentDepth;
}

let maxDepth = currentDepth;
for (const call of trace.calls) {
const depth = this.getMaxDepth(call, currentDepth + 1);
maxDepth = Math.max(maxDepth, depth);
}
return maxDepth;
}

detectMEVPatterns(traces) {
const patterns = [];

// Look for sandwich attacks
for (let i = 1; i < traces.length - 1; i++) {
const prev = traces[i - 1].result;
const curr = traces[i].result;
const next = traces[i + 1].result;

// Simple sandwich detection
if (prev && curr && next) {
if (prev.from === next.from &&
prev.to === next.to &&
prev.from !== curr.from) {
patterns.push({
type: 'potential_sandwich',
transactions: [i - 1, i, i + 1],
attacker: prev.from,
victim: curr.from,
target: prev.to
});
}
}
}

// Look for arbitrage
const addressActivity = {};
traces.forEach((trace, index) => {
const from = trace.result?.from;
if (from) {
if (!addressActivity[from]) {
addressActivity[from] = [];
}
addressActivity[from].push(index);
}
});

// Check for multiple transactions from same address
for (const [address, indices] of Object.entries(addressActivity)) {
if (indices.length >= 2) {
// Check if transactions are sequential or close
const sequential = indices.every((val, i, arr) =>
i === 0 || val === arr[i - 1] + 1
);

if (sequential || indices[indices.length - 1] - indices[0] <= 5) {
patterns.push({
type: 'potential_arbitrage',
transactions: indices,
address: address
});
}
}
}

return patterns;
}

async compareTransactionGas(blockNumber) {
const traces = await this.traceBlock(blockNumber);

// Sort by gas usage
const sorted = traces.transactions
.sort((a, b) => b.gasUsed - a.gasUsed)
.map(tx => ({
hash: tx.hash,
gasUsed: tx.gasUsed,
percentage: ((tx.gasUsed / traces.totalGasUsed) * 100).toFixed(2),
efficiency: tx.success ? 'success' : 'failed'
}));

return {
blockNumber: traces.blockNumber,
totalGas: traces.totalGasUsed,
averageGas: Math.floor(traces.totalGasUsed / traces.transactionCount),
mostExpensive: sorted.slice(0, 5),
leastExpensive: sorted.slice(-5).reverse(),
gasDistribution: this.calculateGasDistribution(traces.transactions)
};
}

calculateGasDistribution(transactions) {
const ranges = {
'under_50k': 0,
'50k_100k': 0,
'100k_500k': 0,
'500k_1m': 0,
'over_1m': 0
};

transactions.forEach(tx => {
const gas = tx.gasUsed;
if (gas < 50000) ranges['under_50k']++;
else if (gas < 100000) ranges['50k_100k']++;
else if (gas < 500000) ranges['100k_500k']++;
else if (gas < 1000000) ranges['500k_1m']++;
else ranges['over_1m']++;
});

return ranges;
}

async analyzeBlockInteractions(blockNumber) {
const traces = await this.traceBlock(blockNumber);

// Build interaction graph
const interactions = new Map();

traces.transactions.forEach(tx => {
if (!tx.from || !tx.to) return;

const key = `${tx.from}->${tx.to}`;
if (!interactions.has(key)) {
interactions.set(key, {
from: tx.from,
to: tx.to,
count: 0,
transactions: [],
totalGas: 0
});
}

const interaction = interactions.get(key);
interaction.count++;
interaction.transactions.push(tx.index);
interaction.totalGas += tx.gasUsed;
});

// Find most active contracts
const contractActivity = {};
traces.transactions.forEach(tx => {
if (tx.to) {
if (!contractActivity[tx.to]) {
contractActivity[tx.to] = {
address: tx.to,
interactions: 0,
gasUsed: 0,
uniqueCallers: new Set()
};
}
contractActivity[tx.to].interactions++;
contractActivity[tx.to].gasUsed += tx.gasUsed;
if (tx.from) {
contractActivity[tx.to].uniqueCallers.add(tx.from);
}
}
});

// Convert sets to counts
Object.values(contractActivity).forEach(activity => {
activity.uniqueCallers = activity.uniqueCallers.size;
});

return {
blockNumber: traces.blockNumber,
uniqueInteractions: interactions.size,
interactions: Array.from(interactions.values())
.sort((a, b) => b.count - a.count)
.slice(0, 10),
mostActiveContracts: Object.values(contractActivity)
.sort((a, b) => b.interactions - a.interactions)
.slice(0, 10)
};
}
}

// MEV detector
class MEVDetector {
constructor(provider) {
this.provider = provider;
this.analyzer = new BlockAnalyzer(provider);
}

async detectMEV(blockNumber) {
const block = await this.provider.getBlock(blockNumber, true);
const traces = await this.analyzer.traceBlock(blockNumber);

const mevOpportunities = {
sandwiches: [],
arbitrage: [],
liquidations: [],
suspiciousPatterns: []
};

// Analyze transaction ordering
for (let i = 0; i < traces.transactions.length; i++) {
const tx = traces.transactions[i];
const receipt = await this.provider.getTransactionReceipt(tx.hash);

// Check for DEX interactions
const isDEX = this.isDEXInteraction(receipt.logs);

if (isDEX && i > 0 && i < traces.transactions.length - 1) {
// Check for sandwich pattern
const prevTx = traces.transactions[i - 1];
const nextTx = traces.transactions[i + 1];

if (this.isSandwichPattern(prevTx, tx, nextTx)) {
mevOpportunities.sandwiches.push({
victim: tx.hash,
frontrun: prevTx.hash,
backrun: nextTx.hash,
victimGas: tx.gasUsed,
attackerProfit: this.estimateProfit(prevTx, nextTx)
});
}
}

// Check for arbitrage
if (this.isArbitragePattern(tx, traces.transactions.slice(i, i + 5))) {
mevOpportunities.arbitrage.push({
transactions: traces.transactions.slice(i, i + 5).map(t => t.hash),
estimatedProfit: 'Unknown'
});
}
}

return mevOpportunities;
}

isDEXInteraction(logs) {
// Check for common DEX event signatures
const dexSignatures = [
'0xd78ad95f', // Swap
'0x1c411e9a', // Sync
'0xdccd412f' // Mint
];

return logs.some(log =>
dexSignatures.some(sig =>
log.topics[0]?.startsWith(sig)
)
);
}

isSandwichPattern(prevTx, victimTx, nextTx) {
return prevTx.from === nextTx.from &&
prevTx.to === victimTx.to &&
nextTx.to === victimTx.to &&
prevTx.from !== victimTx.from;
}

isArbitragePattern(tx, nextTransactions) {
// Simple check: same sender, multiple DEX interactions
const sender = tx.from;
const dexInteractions = nextTransactions.filter(t =>
t.from === sender && t.to !== sender
);

return dexInteractions.length >= 2;
}

estimateProfit(frontTx, backTx) {
// Simplified profit estimation
return 'Requires detailed token analysis';
}
}

// Usage examples
const analyzer = new BlockAnalyzer(provider);

// Analyze entire block
const blockAnalysis = await analyzer.traceBlock(12345678);
console.log('Block transactions:', blockAnalysis.transactionCount);
console.log('Total gas used:', blockAnalysis.totalGasUsed);
console.log('MEV patterns detected:', blockAnalysis.patterns.mev);

// Compare gas usage
const gasComparison = await analyzer.compareTransactionGas(12345678);
console.log('Most expensive tx:', gasComparison.mostExpensive[0]);

// Analyze interactions
const interactions = await analyzer.analyzeBlockInteractions(12345678);
console.log('Most active contract:', interactions.mostActiveContracts[0]);

// Detect MEV
const mevDetector = new MEVDetector(provider);
const mevResults = await mevDetector.detectMEV(12345678);
console.log('Sandwich attacks found:', mevResults.sandwiches.length);

Common Use Cases​

1. Block-Wide Gas Analysis​

// Analyze gas patterns across a block
async function analyzeBlockGasPatterns(blockNumber) {
const analyzer = new BlockAnalyzer(provider);
const analysis = await analyzer.compareTransactionGas(blockNumber);

// Identify optimization opportunities
const recommendations = [];

// Check for inefficient gas usage
if (analysis.gasDistribution.over_1m > 0) {
recommendations.push({
issue: 'Very high gas transactions detected',
count: analysis.gasDistribution.over_1m,
suggestion: 'Review complex operations for optimization'
});
}

// Check average vs median
const avgGas = analysis.averageGas;
const medianGas = Math.floor(
analysis.totalGas / analysis.mostExpensive.length
);

if (avgGas > medianGas * 2) {
recommendations.push({
issue: 'Gas usage highly skewed',
suggestion: 'A few transactions consuming disproportionate gas'
});
}

return {
blockNumber: blockNumber,
insights: recommendations,
topGasConsumers: analysis.mostExpensive
};
}

2. MEV Impact Assessment​

// Assess MEV impact on regular users
async function assessMEVImpact(blockNumber) {
const mevDetector = new MEVDetector(provider);
const mevResults = await mevDetector.detectMEV(blockNumber);

const impact = {
sandwichVictims: mevResults.sandwiches.length,
estimatedLosses: 0,
affectedUsers: new Set(),
mevTransactions: [],
regularTransactions: []
};

// Calculate impact
for (const sandwich of mevResults.sandwiches) {
impact.affectedUsers.add(
sandwich.victim.substring(0, 42)
);

// Add to MEV transactions
impact.mevTransactions.push(sandwich.frontrun);
impact.mevTransactions.push(sandwich.backrun);
}

// Get all transactions in block
const block = await provider.getBlock(blockNumber, true);

// Separate MEV from regular transactions
for (const tx of block.transactions) {
if (!impact.mevTransactions.includes(tx.hash)) {
impact.regularTransactions.push(tx.hash);
}
}

return {
blockNumber: blockNumber,
mevRatio: (
impact.mevTransactions.length /
block.transactions.length * 100
).toFixed(2) + '%',
victimsCount: impact.affectedUsers.size,
regularUsersAffected: impact.sandwichVictims
};
}

3. State Change Timeline​

// Build timeline of state changes in block
async function buildStateTimeline(blockNumber) {
const analyzer = new BlockAnalyzer(provider);
const traces = await analyzer.traceBlock(blockNumber);

const timeline = [];

for (const tx of traces.transactions) {
timeline.push({
index: tx.index,
timestamp: traces.timestamp + tx.index, // Approximate
from: tx.from,
to: tx.to,
type: tx.success ? 'success' : 'failed',
gasUsed: tx.gasUsed,
impact: tx.callCount > 5 ? 'high' :
tx.callCount > 2 ? 'medium' : 'low'
});
}

return {
blockNumber: blockNumber,
blockTime: traces.timestamp,
events: timeline,
summary: {
totalTransactions: timeline.length,
successRate: (
timeline.filter(e => e.type === 'success').length /
timeline.length * 100
).toFixed(2) + '%'
}
};
}

Error Handling​

Error TypeDescriptionSolution
Block not foundInvalid block numberVerify block exists
TimeoutBlock too large to traceReduce timeout or use pagination
Memory limitToo many transactionsProcess in batches
async function safeBlockTrace(blockNumber, batchSize = 10) {
try {
// Try full block trace
return await provider.send('debug_traceBlock', [
blockNumber,
{
tracer: 'callTracer',
tracerConfig: {
onlyTopCall: false,
timeout: '30s'
}
}
]);
} catch (error) {
if (error.message.includes('timeout')) {
// Fall back to batched processing
const block = await provider.getBlock(blockNumber);
const traces = [];

for (let i = 0; i < block.transactions.length; i += batchSize) {
const batch = block.transactions.slice(i, i + batchSize);

for (const txHash of batch) {
const trace = await provider.send('debug_traceTransaction', [
txHash,
{
tracer: 'callTracer',
tracerConfig: { onlyTopCall: true }
}
]);
traces.push(trace);
}
}

return traces;
}

throw error;
}
}

Need help? Contact our support team for debug method access or check the Base documentation.