From 382031b8dd0283b2f140bdf22a0cb6003e412b0d Mon Sep 17 00:00:00 2001 From: Matty Evans Date: Sat, 20 Dec 2025 18:59:33 +1000 Subject: [PATCH] feat(payloads): enable deep-linking by block number and add Tracoor links Allow direct navigation to any block by skipping time filtering when blockNumber is provided in the URL. This lets users share links to blocks outside the default 6-hour window. Add optional Tracoor icon column that links to external execution block traces when the network config provides a tracoor service URL. --- .../ethereum/execution/payloads/IndexPage.tsx | 18 ++++++++-- .../components/PayloadsView/PayloadsView.tsx | 33 ++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/pages/ethereum/execution/payloads/IndexPage.tsx b/src/pages/ethereum/execution/payloads/IndexPage.tsx index bf308c4a2..be5eed0ec 100644 --- a/src/pages/ethereum/execution/payloads/IndexPage.tsx +++ b/src/pages/ethereum/execution/payloads/IndexPage.tsx @@ -29,10 +29,20 @@ export function IndexPage(): JSX.Element { // Calculate time range in seconds // Use URL params if provided, otherwise default to last hour with no upper bound + // Skip time filtering when blockNumber is provided for deep-linking const timeRange = useMemo(() => { const nowSeconds = Math.floor(Date.now() / 1000); const defaultRangeSeconds = DEFAULT_TIME_RANGE_HOURS * 60 * 60; + // When blockNumber is provided, skip time filtering to allow deep-linking + // to blocks that fall outside the default 6-hour window + if (search.blockNumber !== undefined) { + return { + start: undefined, + end: undefined, + }; + } + if (search.timeStart !== undefined && search.timeEnd !== undefined) { return { start: search.timeStart, @@ -58,7 +68,7 @@ export function IndexPage(): JSX.Element { start: nowSeconds - defaultRangeSeconds, end: undefined, }; - }, [search.timeStart, search.timeEnd]); + }, [search.timeStart, search.timeEnd, search.blockNumber]); // Check if live mode is enabled from URL (only if feature is enabled) const isLive = LIVE_MODE_ENABLED && (search.isLive ?? false); @@ -85,7 +95,7 @@ export function IndexPage(): JSX.Element { } = useQuery({ ...intEngineNewPayloadServiceListOptions({ query: { - slot_start_date_time_gte: timeRange.start, + ...(timeRange.start !== undefined && { slot_start_date_time_gte: timeRange.start }), ...(timeRange.end !== undefined && { slot_start_date_time_lte: timeRange.end }), duration_ms_gte: durationMin, page_size: search.pageSize ?? DEFAULT_PAGE_SIZE, @@ -348,6 +358,9 @@ export function IndexPage(): JSX.Element { ); }, [data, search.detailSlot, search.detailNodeName]); + // Get tracoor URL from network config for external links + const tracoorUrl = currentNetwork?.service_urls?.tracoor; + return ( void; // Duration threshold for display durationThreshold: number; + // External links + tracoorUrl?: string; // Live mode props isLive?: boolean; onLiveModeToggle?: () => void; @@ -177,6 +180,7 @@ export function PayloadsView({ onFiltersChange, onClearFilters, durationThreshold, + tracoorUrl, isLive = false, onLiveModeToggle, newItemIdsRef, @@ -289,8 +293,35 @@ export function PayloadsView({ }, sortingFn: 'basic', }), + // Tracoor external link column (only shown if tracoorUrl is available) + ...(tracoorUrl + ? [ + columnHelper.display({ + id: 'tracoor', + header: '', + cell: info => { + const blockNumber = info.row.original.block_number; + if (blockNumber === undefined) return null; + const url = `${tracoorUrl}/execution_block_trace?executionBlockTraceBlockNumber=${blockNumber}`; + return ( + e.stopPropagation()} + className="inline-flex items-center transition-opacity hover:opacity-70" + title="View in Tracoor" + > + + + ); + }, + enableSorting: false, + }), + ] + : []), ], - [onFilterClick] + [onFilterClick, tracoorUrl] ); // Get row ID for live mode highlighting