From 04c7428012bd5eb61e75e615aea02c25fdf592bf Mon Sep 17 00:00:00 2001 From: Niran Babalola Date: Fri, 27 Mar 2026 23:27:04 -0500 Subject: [PATCH 1/2] Fix per-tx gasUsed to report actual gas consumed, not gas limit The block API was storing tx.gas (the gas limit from the transaction envelope) as gasUsed for each transaction. The correct value was already available in MeterBundleResult.gasUsed from the bundle history in S3. Now includes both fields per transaction: - gasLimit: from the transaction envelope (always available) - gasUsed: actual gas consumed from meter data (null when unavailable) The UI shows "gasUsed / gasLimit gas" when meter data exists, or "gasLimit gas limit" for transactions without it (e.g. L1 deposit tx). Cache deserialization is backwards-compatible with existing cached blocks that only have the old gasUsed field (interpreted as gasLimit). --- src/app/api/block/[hash]/route.ts | 31 ++++++++++++++++++------------- src/app/block/[hash]/page.tsx | 4 +++- src/lib/s3.ts | 8 +++++--- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/app/api/block/[hash]/route.ts b/src/app/api/block/[hash]/route.ts index bf48204..65e624c 100644 --- a/src/app/api/block/[hash]/route.ts +++ b/src/app/api/block/[hash]/route.ts @@ -20,7 +20,8 @@ function serializeBlockData(block: BlockData) { gasLimit: block.gasLimit.toString(), transactions: block.transactions.map((tx) => ({ ...tx, - gasUsed: tx.gasUsed.toString(), + gasLimit: tx.gasLimit.toString(), + gasUsed: tx.gasUsed?.toString() ?? null, })), }; } @@ -73,16 +74,16 @@ async function buildAndCacheBlockData( ): Promise { const transactions: BlockTransaction[] = await Promise.all( rpcBlock.transactions.map(async (tx, index) => { - const { bundleId, executionTimeUs, stateRootTimeUs } = - await enrichTransactionWithBundleData(tx.hash); + const enriched = await enrichTransactionWithBundleData(tx.hash); return { hash: tx.hash, from: tx.from, to: tx.to, - gasUsed: tx.gas, - executionTimeUs, - stateRootTimeUs, - bundleId, + gasLimit: tx.gas, + gasUsed: enriched.gasUsed != null ? BigInt(enriched.gasUsed) : null, + executionTimeUs: enriched.executionTimeUs, + stateRootTimeUs: enriched.stateRootTimeUs, + bundleId: enriched.bundleId, index, }; }), @@ -114,23 +115,24 @@ async function enrichTransactionWithBundleData(txHash: string): Promise<{ bundleId: string | null; executionTimeUs: number | null; stateRootTimeUs: number | null; + gasUsed: number | null; }> { const metadata = await getTransactionMetadataByHash(txHash); if (!metadata || metadata.bundle_ids.length === 0) { - return { bundleId: null, executionTimeUs: null, stateRootTimeUs: null }; + return { bundleId: null, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; } const bundleId = metadata.bundle_ids[0]; const bundleHistory = await getBundleHistory(bundleId); if (!bundleHistory) { - return { bundleId, executionTimeUs: null, stateRootTimeUs: null }; + return { bundleId, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; } const receivedEvent = bundleHistory.history.find( (e) => e.event === "Received", ); if (!receivedEvent?.data?.bundle?.meter_bundle_response?.results) { - return { bundleId, executionTimeUs: null, stateRootTimeUs: null }; + return { bundleId, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; } const meterResponse = receivedEvent.data.bundle.meter_bundle_response; @@ -147,6 +149,7 @@ async function enrichTransactionWithBundleData(txHash: string): Promise<{ bundleId, executionTimeUs: txResult?.executionTimeUs ?? null, stateRootTimeUs: meterResponse.stateRootTimeUs ?? null, + gasUsed: txResult?.gasUsed ?? null, }; } @@ -163,9 +166,8 @@ async function refetchMissingTransactionSimulations( const refetchResults = await Promise.all( transactionsToRefetch.map(async (tx) => { - const { bundleId, executionTimeUs, stateRootTimeUs } = - await enrichTransactionWithBundleData(tx.hash); - return { hash: tx.hash, bundleId, executionTimeUs, stateRootTimeUs }; + const enriched = await enrichTransactionWithBundleData(tx.hash); + return { hash: tx.hash, ...enriched }; }), ); @@ -179,6 +181,9 @@ async function refetchMissingTransactionSimulations( bundleId: refetchResult.bundleId, executionTimeUs: refetchResult.executionTimeUs, stateRootTimeUs: refetchResult.stateRootTimeUs, + ...(refetchResult.gasUsed != null && { + gasUsed: BigInt(refetchResult.gasUsed), + }), }; } return tx; diff --git a/src/app/block/[hash]/page.tsx b/src/app/block/[hash]/page.tsx index 71bcca7..b159cdb 100644 --- a/src/app/block/[hash]/page.tsx +++ b/src/app/block/[hash]/page.tsx @@ -152,7 +152,9 @@ function TransactionRow({
)}
- {tx.gasUsed.toLocaleString()} gas + {tx.gasUsed != null + ? `${tx.gasUsed.toLocaleString()} / ${tx.gasLimit.toLocaleString()} gas` + : `${tx.gasLimit.toLocaleString()} gas limit`}
diff --git a/src/lib/s3.ts b/src/lib/s3.ts index 60f8f3a..f52d2c3 100644 --- a/src/lib/s3.ts +++ b/src/lib/s3.ts @@ -184,7 +184,8 @@ export interface BlockTransaction { hash: string; from: string; to: string | null; - gasUsed: bigint; + gasLimit: bigint; + gasUsed: bigint | null; executionTimeUs: number | null; stateRootTimeUs: number | null; bundleId: string | null; @@ -220,9 +221,10 @@ export async function getBlockFromCache( gasUsed: BigInt(parsed.gasUsed), gasLimit: BigInt(parsed.gasLimit), transactions: parsed.transactions.map( - (tx: BlockTransaction & { gasUsed: string }) => ({ + (tx: { gasLimit?: string; gasUsed?: string | null; [key: string]: unknown }) => ({ ...tx, - gasUsed: BigInt(tx.gasUsed), + gasLimit: BigInt(tx.gasLimit ?? tx.gasUsed ?? "0"), + gasUsed: tx.gasUsed != null ? BigInt(tx.gasUsed) : null, }), ), } as BlockData; From e110e9f06e64d6139242de17d7494b16176e7a35 Mon Sep 17 00:00:00 2001 From: Niran Babalola Date: Mon, 30 Mar 2026 11:40:43 -0500 Subject: [PATCH 2/2] Fix lint formatting for multi-field return statements --- src/app/api/block/[hash]/route.ts | 21 ++++++++++++++++++--- src/lib/s3.ts | 6 +++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/app/api/block/[hash]/route.ts b/src/app/api/block/[hash]/route.ts index 65e624c..dcfc94c 100644 --- a/src/app/api/block/[hash]/route.ts +++ b/src/app/api/block/[hash]/route.ts @@ -119,20 +119,35 @@ async function enrichTransactionWithBundleData(txHash: string): Promise<{ }> { const metadata = await getTransactionMetadataByHash(txHash); if (!metadata || metadata.bundle_ids.length === 0) { - return { bundleId: null, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; + return { + bundleId: null, + executionTimeUs: null, + stateRootTimeUs: null, + gasUsed: null, + }; } const bundleId = metadata.bundle_ids[0]; const bundleHistory = await getBundleHistory(bundleId); if (!bundleHistory) { - return { bundleId, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; + return { + bundleId, + executionTimeUs: null, + stateRootTimeUs: null, + gasUsed: null, + }; } const receivedEvent = bundleHistory.history.find( (e) => e.event === "Received", ); if (!receivedEvent?.data?.bundle?.meter_bundle_response?.results) { - return { bundleId, executionTimeUs: null, stateRootTimeUs: null, gasUsed: null }; + return { + bundleId, + executionTimeUs: null, + stateRootTimeUs: null, + gasUsed: null, + }; } const meterResponse = receivedEvent.data.bundle.meter_bundle_response; diff --git a/src/lib/s3.ts b/src/lib/s3.ts index f52d2c3..9086f70 100644 --- a/src/lib/s3.ts +++ b/src/lib/s3.ts @@ -221,7 +221,11 @@ export async function getBlockFromCache( gasUsed: BigInt(parsed.gasUsed), gasLimit: BigInt(parsed.gasLimit), transactions: parsed.transactions.map( - (tx: { gasLimit?: string; gasUsed?: string | null; [key: string]: unknown }) => ({ + (tx: { + gasLimit?: string; + gasUsed?: string | null; + [key: string]: unknown; + }) => ({ ...tx, gasLimit: BigInt(tx.gasLimit ?? tx.gasUsed ?? "0"), gasUsed: tx.gasUsed != null ? BigInt(tx.gasUsed) : null,