Skip to content

Commit 0d7d110

Browse files
committed
feat: implement OptimismCard component for displaying decoded transaction fields and removes autocompleteBlockHash
1 parent 9599958 commit 0d7d110

File tree

4 files changed

+117
-121
lines changed

4 files changed

+117
-121
lines changed

apps/web/src/pages/tx/[hash].tsx

+92-73
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { useMemo } from "react";
33
import type { NextPage } from "next";
44
import { useRouter } from "next/router";
55

6+
import type { OptimismDecodedData } from "@blobscan/api/src/blob-parse/optimism";
7+
import type dayjs from "@blobscan/dayjs";
8+
69
import { RollupBadge } from "~/components/Badges/RollupBadge";
710
import { Card } from "~/components/Cards/Card";
811
import { BlobCard } from "~/components/Cards/SurfaceCards/BlobCard";
@@ -16,6 +19,7 @@ import { Link } from "~/components/Link";
1619
import { NavArrows } from "~/components/NavArrows";
1720
import { BlockStatus } from "~/components/Status";
1821
import { api } from "~/api-client";
22+
import Loading from "~/icons/loading.svg";
1923
import NextError from "~/pages/_error";
2024
import type { TransactionWithExpandedBlockAndBlob } from "~/types";
2125
import {
@@ -256,62 +260,10 @@ const Tx: NextPage = () => {
256260
/>
257261

258262
{decodedData && (
259-
<Card header="Decoded Fields">
260-
<div>
261-
<InfoGrid
262-
fields={[
263-
{
264-
name: "Timestamp since L2 genesis",
265-
value: (
266-
<div className="whitespace-break-spaces">
267-
{tx
268-
? formatTimestamp(
269-
tx.blockTimestamp.subtract(
270-
decodedData.timestampSinceL2Genesis,
271-
"ms"
272-
)
273-
)
274-
: ""}
275-
</div>
276-
),
277-
},
278-
{
279-
name: "Last L1 origin number",
280-
value: decodedData.lastL1OriginNumber,
281-
},
282-
{
283-
name: "Parent L2 block hash",
284-
value: "0x" + decodedData.parentL2BlockHash + "...",
285-
},
286-
{
287-
name: "L1 origin block hash",
288-
value: (
289-
<BlockHash
290-
fullHash={decodedData.fullL1OriginBlockHash}
291-
partialHash={decodedData.l1OriginBlockHash}
292-
/>
293-
),
294-
},
295-
{
296-
name: "Number of L2 blocks",
297-
value: decodedData.numberOfL2Blocks,
298-
},
299-
{
300-
name: "Changed by L1 origin",
301-
value: decodedData.changedByL1Origin,
302-
},
303-
{
304-
name: "Total transactions",
305-
value: decodedData.totalTxs,
306-
},
307-
{
308-
name: "Contract creation transactions",
309-
value: decodedData.contractCreationTxsNumber,
310-
},
311-
]}
312-
/>
313-
</div>
314-
</Card>
263+
<OptimismCard
264+
decodedData={decodedData}
265+
txTimestamp={tx ? tx.blockTimestamp : undefined}
266+
/>
315267
)}
316268

317269
<Card header={`Blobs ${tx ? `(${tx.blobs.length})` : ""}`}>
@@ -325,28 +277,95 @@ const Tx: NextPage = () => {
325277
);
326278
};
327279

328-
type BlockHashProps = {
329-
partialHash: string;
330-
fullHash: string | undefined;
280+
type OptimismCardProps = {
281+
decodedData: OptimismDecodedData;
282+
txTimestamp: dayjs.Dayjs | undefined;
331283
};
332284

333-
const BlockHash: FC<BlockHashProps> = ({ fullHash, partialHash }) => {
334-
if (fullHash === undefined) {
335-
return "0x" + partialHash + "...";
336-
}
285+
const OptimismCard: FC<OptimismCardProps> = ({ decodedData, txTimestamp }) => {
286+
const { data: blockExists, isLoading } = api.block.checkBlockExists.useQuery({
287+
blockNumber: decodedData.lastL1OriginNumber,
288+
});
289+
290+
const blockLink = blockExists
291+
? `https://blobscan.com/block/${decodedData.lastL1OriginNumber}`
292+
: `https://etherscan.io/block/${decodedData.lastL1OriginNumber}`;
293+
294+
const hash = `0x${decodedData.l1OriginBlockHash}...`;
295+
296+
const timestamp = txTimestamp
297+
? formatTimestamp(
298+
txTimestamp.subtract(decodedData.timestampSinceL2Genesis, "ms")
299+
)
300+
: undefined;
337301

338-
const prefixedFullHash = "0x" + fullHash;
302+
if (isLoading) {
303+
return (
304+
<Card header="Loading Decoded Fields...">
305+
<div className="flex h-32 items-center justify-center">
306+
<Loading className="h-8 w-8 animate-spin" />
307+
</div>
308+
</Card>
309+
);
310+
}
339311

340312
return (
341-
<div className="flex items-center gap-2">
342-
<Link href={`https://blobscan.com/block/${prefixedFullHash}`}>
343-
{prefixedFullHash}
344-
</Link>
345-
<CopyToClipboard
346-
value={prefixedFullHash}
347-
tooltipText="Copy L1 origin block hash"
348-
/>
349-
</div>
313+
<Card header="Decoded Fields">
314+
<div>
315+
<InfoGrid
316+
fields={[
317+
{
318+
name: "Timestamp since L2 genesis",
319+
value: <div className="whitespace-break-spaces">{timestamp}</div>,
320+
},
321+
{
322+
name: "Last L1 origin number",
323+
value: (
324+
<div className="flex items-center gap-2">
325+
<Link href={blockLink}>{decodedData.lastL1OriginNumber}</Link>
326+
<CopyToClipboard
327+
value={decodedData.lastL1OriginNumber.toString()}
328+
tooltipText="Copy Last L1 origin number"
329+
/>
330+
</div>
331+
),
332+
},
333+
{
334+
name: "Parent L2 block hash",
335+
value: "0x" + decodedData.parentL2BlockHash + "...",
336+
},
337+
{
338+
name: "L1 origin block hash",
339+
value: (
340+
<div className="flex items-center gap-2">
341+
<Link href={blockLink}>{hash}</Link>
342+
<CopyToClipboard
343+
value={hash}
344+
tooltipText="Copy L1 origin block hash"
345+
/>
346+
</div>
347+
),
348+
},
349+
{
350+
name: "Number of L2 blocks",
351+
value: decodedData.numberOfL2Blocks,
352+
},
353+
{
354+
name: "Changed by L1 origin",
355+
value: decodedData.changedByL1Origin,
356+
},
357+
{
358+
name: "Total transactions",
359+
value: decodedData.totalTxs,
360+
},
361+
{
362+
name: "Contract creation transactions",
363+
value: decodedData.contractCreationTxsNumber,
364+
},
365+
]}
366+
/>
367+
</div>
368+
</Card>
350369
);
351370
};
352371

+1-48
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { z } from "zod";
22

3-
import { prisma } from "@blobscan/db";
4-
import { logger } from "@blobscan/logger";
5-
63
export const OptimismDecodedDataSchema = z.object({
74
timestampSinceL2Genesis: z.number(),
85
lastL1OriginNumber: z.number(),
@@ -12,10 +9,9 @@ export const OptimismDecodedDataSchema = z.object({
129
changedByL1Origin: z.number(),
1310
totalTxs: z.number(),
1411
contractCreationTxsNumber: z.number(),
15-
fullL1OriginBlockHash: z.string().optional(),
1612
});
1713

18-
type OptimismDecodedData = z.infer<typeof OptimismDecodedDataSchema>;
14+
export type OptimismDecodedData = z.infer<typeof OptimismDecodedDataSchema>;
1915

2016
export async function parseOptimismDecodedData(
2117
data: string
@@ -34,48 +30,5 @@ export async function parseOptimismDecodedData(
3430
return null;
3531
}
3632

37-
const hash = await autocompleteBlockHash(decoded.data.l1OriginBlockHash);
38-
39-
if (hash) {
40-
decoded.data.fullL1OriginBlockHash = hash;
41-
} else {
42-
logger.error(
43-
`Failed to get full block hash for L1 origin block hash: ${decoded.data.l1OriginBlockHash}`
44-
);
45-
}
46-
4733
return decoded.data;
4834
}
49-
50-
/* Autocomplete a block hash from a truncated version of it.
51-
@param partialHash - The first bytes of a block hash.
52-
@returns The block hash, if there is a single ocurrence, or null.
53-
*/
54-
async function autocompleteBlockHash(partialHash: string) {
55-
if (!partialHash) {
56-
return null;
57-
}
58-
59-
const blocks = await prisma.block.findMany({
60-
where: {
61-
hash: {
62-
startsWith: "0x" + partialHash,
63-
},
64-
},
65-
select: {
66-
hash: true,
67-
},
68-
});
69-
70-
if (blocks[0] === undefined) {
71-
return null;
72-
}
73-
74-
if (blocks.length > 1) {
75-
logger.error(
76-
`Found ${blocks.length} blocks while autocompleting block hash ${partialHash}`
77-
);
78-
}
79-
80-
return blocks[0].hash;
81-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { z } from "@blobscan/zod";
2+
3+
import { publicProcedure } from "../../procedures";
4+
5+
export const checkBlockExists = publicProcedure
6+
.input(
7+
z.object({
8+
blockNumber: z.number(),
9+
})
10+
)
11+
.query(async ({ ctx: { prisma }, input }) => {
12+
const block = await prisma.block.findFirst({
13+
where: {
14+
number: input.blockNumber,
15+
},
16+
select: {
17+
number: true,
18+
},
19+
});
20+
21+
return Boolean(block);
22+
});

packages/api/src/routers/block/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { t } from "../../trpc-client";
2+
import { checkBlockExists } from "./checkBlobExists";
23
import { getAll } from "./getAll";
34
import { getByBlockId } from "./getByBlockId";
45
import { getCount } from "./getCount";
@@ -9,4 +10,5 @@ export const blockRouter = t.router({
910
getByBlockId,
1011
getCount,
1112
getLatestBlock,
13+
checkBlockExists,
1214
});

0 commit comments

Comments
 (0)