Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4c53bf4
fix: update error boundary support link to help center (#262)
paulbalaji Jan 29, 2026
1411bdb
feat: add transaction page to view all messages in a tx
yorhodes Jan 28, 2026
8aee94d
fix: address PR review comments for tx page
yorhodes Jan 29, 2026
e5e15ba
Merge feat/ica-decoding into feat/tx-page
yorhodes Jan 29, 2026
f5fa4d3
fix: constrain tx page width for better layout
yorhodes Jan 29, 2026
01cf382
ci: add Claude code review workflow (#264)
paulbalaji Jan 30, 2026
8f44ffc
Add AGENTS.md as duplicate of CLAUDE.md (#266)
paulbalaji Feb 3, 2026
d4c0480
feat: add warp route ID search and pending message status filter (#263)
nambrot-agent Feb 4, 2026
c2b6162
chore(ci): update Claude model to opus 4.6 (#267)
paulbalaji Feb 5, 2026
1cd7315
chore: remove unused deps, unnecessary SSR, and dead api-docs route (…
paulbalaji Feb 5, 2026
7fab189
feat(ci): use skills for claude review workflows (#270)
paulbalaji Feb 5, 2026
123a219
perf(build): skip lint/typecheck during Vercel build (#269)
paulbalaji Feb 5, 2026
2ef0be0
chore: update Hyperlane deps (#245)
hyper-gonk[bot] Feb 5, 2026
007ddce
fix: show viction in explorer chain dropdown (#271)
paulbalaji Feb 7, 2026
e044a9c
fix: aleo tx formatting (#276)
troykessler Feb 11, 2026
5d7d1d4
fix(table): also show aleo warp info (#277)
yjamin Feb 16, 2026
08fca6d
feat: add warp route visualization card for message details (#257)
nambrot-agent Feb 17, 2026
8e4cf67
perf(build): enable parallel build flags (#275)
paulbalaji Feb 17, 2026
9251afe
refactor: clean up message details UI (#260)
yorhodes Feb 25, 2026
f477bc2
chore: update dependencies to latest (#282)
yjamin Feb 26, 2026
98f1f58
chore: update Hyperlane deps (#283)
hyper-gonk[bot] Mar 2, 2026
f7d18d5
fix: xERC20 lockbox collateral checks and VS variant support (#284)
paulbalaji Mar 3, 2026
785d88a
chore: update Hyperlane deps (#285)
hyper-gonk[bot] Mar 5, 2026
92c3525
fix: title for seo (#286)
troykessler Mar 5, 2026
fa3fd9e
fix: add key props to OG meta tags for deduplication (#288)
paulbalaji Mar 5, 2026
bb93bcf
fix: aleo address format (#289)
yjamin Mar 6, 2026
7395e75
chore: add REVIEW.md, update AI review docs (#291)
paulbalaji Mar 9, 2026
105e479
fix(ci): enable inline PR review comments for claude review workflows…
paulbalaji Mar 12, 2026
8fa6f07
chore: update Hyperlane deps (#293)
hyper-gonk[bot] Mar 12, 2026
b79ab39
feat: ui rebrand to match nexus v2 (#248)
paulbalaji Mar 16, 2026
f1254d2
feat: upgrade to TypeScript 6 (#299)
paulbalaji Mar 24, 2026
4a53795
chore: update brand link (#300)
paulbalaji Mar 25, 2026
ae53fb7
chore: update OG preview image to new brand artwork (#302)
paulbalaji Mar 26, 2026
9b8f72f
perf: reduce initial explorer bundles (#295)
paulbalaji Mar 27, 2026
a28b3f8
chore: Next 16, React 19, oxlint/oxfmt (#303)
paulbalaji Mar 27, 2026
f705c44
fix: restore header perf loading behavior (#304)
paulbalaji Mar 27, 2026
bce4884
ci: enforce minimum package release age in pnpm (#305)
paulbalaji Mar 31, 2026
a6aeab0
fix: dynamic OG meta tags for message pages (#306)
paulbalaji Mar 31, 2026
7c1f829
chore: update Hyperlane deps (#298)
hyper-gonk[bot] Apr 6, 2026
c3fbb85
fix: dont show aleo when loading message (#307)
troykessler Apr 10, 2026
fc391bf
feat: show distinct origin/destination tokens in warp transfer views …
Xaroz Apr 13, 2026
707deff
chore: update Hyperlane deps (#309)
hyper-gonk[bot] Apr 13, 2026
23f7903
Merge branch 'main' into feat/tx-page
yorhodes Apr 13, 2026
fe5c990
Fixes
yorhodes Apr 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/components/search/SearchStates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,22 @@ export function SearchChainError({ show }: { show: boolean }) {
/>
);
}

export function SearchRedirecting({ show }: { show: boolean }) {
return (
<div className="absolute left-0 right-0 top-10">
<Fade show={show}>
<div className="my-10 flex justify-center">
<div className="flex max-w-md flex-col items-center justify-center px-3 py-5">
<div className="flex items-center justify-center">
<SpinnerIcon width={40} height={40} />
</div>
<div className="mt-4 text-center font-light leading-loose text-gray-700">
Found it! Redirecting...
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in practice this text never actually shows up and it just flashes me from the search page to the tx page

</div>
</div>
</div>
</Fade>
</div>
);
}
29 changes: 22 additions & 7 deletions src/features/messages/MessageDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { toTitleCase, trimToLength } from '@hyperlane-xyz/utils';
import { SpinnerIcon } from '@hyperlane-xyz/widgets';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useMemo } from 'react';
import { toast } from 'react-toastify';
import { Card } from '../../components/layout/Card';
Expand All @@ -21,7 +22,7 @@ import { WarpTransferDetailsCard } from './cards/WarpTransferDetailsCard';
import { useIsIcaMessage } from './ica';
import { usePiChainMessageQuery } from './pi-queries/usePiChainMessageQuery';
import { PLACEHOLDER_MESSAGE } from './placeholderMessages';
import { useMessageQuery } from './queries/useMessageQuery';
import { useMessageQuery, useTransactionMessageCount } from './queries/useMessageQuery';
import { parseWarpRouteMessageDetails } from './utils';

interface Props {
Expand Down Expand Up @@ -96,15 +97,29 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
[message, warpRouteChainAddressMap, multiProvider],
);

// Check if there are multiple messages in this origin transaction
const txMessageCount = useTransactionMessageCount(origin?.hash);
const showTxLink = txMessageCount > 1;

return (
<>
<Card className="flex items-center justify-between rounded-full px-1">
<h2 className="font-medium text-blue-500">{`${
isIcaMsg ? 'ICA ' : ''
} Message ${trimToLength(msgId, 6)} to ${getChainDisplayName(
multiProvider,
destinationChainName,
)}`}</h2>
<div className="flex items-center gap-3">
<h2 className="font-medium text-blue-500">{`${
isIcaMsg ? 'ICA ' : ''
} Message ${trimToLength(msgId, 6)} to ${getChainDisplayName(
multiProvider,
destinationChainName,
)}`}</h2>
{showTxLink && (
<Link
href={`/tx/${origin.hash}`}
className="text-sm text-gray-500 transition-colors hover:text-gray-700"
>
View all {txMessageCount} messages in tx →
</Link>
)}
Comment on lines +113 to +116
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the msg count is inaccurate (probs need to increase the fetch size or something?)

Image

</div>
<StatusHeader
messageStatus={status}
isMessageFound={isMessageFound}
Expand Down
60 changes: 54 additions & 6 deletions src/features/messages/MessageSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';

import { Fade, IconButton, RefreshIcon, useDebounce } from '@hyperlane-xyz/widgets';
Expand All @@ -10,6 +11,7 @@ import {
SearchEmptyError,
SearchFetching,
SearchInvalidError,
SearchRedirecting,
SearchUnknownError,
} from '../../components/search/SearchStates';
import { useReadyMultiProvider } from '../../store';
Expand Down Expand Up @@ -130,6 +132,46 @@ export function MessageSearch() {
const isAnyMessageFound = isMessagesFound || isPiMessagesFound;
const messageListResult = isMessagesFound ? messageList : piMessageList;

// Compute redirect URL for direct message/tx lookups
const router = useRouter();
const redirectUrl = (() => {
// Wait for queries to complete
if (!hasAllRun || isAnyFetching) return null;

// Don't redirect without user input (prevents redirect on homepage with latest messages)
if (!hasInput) return null;

// Don't redirect if filters are applied
if (originChainFilter || destinationChainFilter || startTimeFilter || endTimeFilter)
return null;

// Need at least one result
if (!messageListResult.length) return null;

const firstMessage = messageListResult[0];

// Single result → always go to message page
if (messageListResult.length === 1) {
return `/message/${firstMessage.msgId}`;
}
Comment thread
yorhodes marked this conversation as resolved.

// Multiple results + origin tx hash match → go to tx page
// Only redirect if GraphQL found results (tx page uses GraphQL only, not PI)
const inputLower = sanitizedInput.toLowerCase();
if (isMessagesFound && firstMessage.origin?.hash?.toLowerCase() === inputLower) {
return `/tx/${firstMessage.origin.hash}`;
}

return null;
})();
Comment on lines +229 to +258
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than using a IIFE, I'd prefer if we just wrap it in a memo hook, this ensure it only applies when state changes happen


// Perform the redirect
useEffect(() => {
if (redirectUrl) {
router.replace(redirectUrl);
}
}, [redirectUrl, router]);
Comment on lines +261 to +265
Copy link
Copy Markdown
Contributor

@Xaroz Xaroz Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use router.push, using replace will "replace" the current stack history and if I wanted to go back it wouldn't work, although with push we might run into an issue with the search looping itself 🤔, still if I press back I'd like to go back to the previous page


// Show message list if there are no errors and filters are valid
const showMessageTable =
!isAnyError &&
Expand Down Expand Up @@ -176,22 +218,28 @@ export function MessageSearch() {
<RefreshButton loading={isAnyFetching} onClick={refetch} />
</div>
</div>
<Fade show={showMessageTable}>
<SearchRedirecting show={!!redirectUrl} />
<Fade show={showMessageTable && !redirectUrl}>
<MessageTable messageList={messageListResult} isFetching={isAnyFetching} />
</Fade>
<SearchFetching
show={!isAnyError && isValidInput && !isAnyMessageFound && !hasAllRun}
show={!redirectUrl && !isAnyError && isValidInput && !isAnyMessageFound && !hasAllRun}
isPiFetching={isPiFetching}
/>
<SearchEmptyError
show={!isAnyError && isValidInput && !isAnyMessageFound && hasAllRun}
show={!redirectUrl && !isAnyError && isValidInput && !isAnyMessageFound && hasAllRun}
hasInput={hasInput}
allowAddress={true}
/>
<SearchUnknownError show={isAnyError && isValidInput} />
<SearchInvalidError show={!isValidInput} allowAddress={true} />
<SearchUnknownError show={!redirectUrl && isAnyError && isValidInput} />
<SearchInvalidError show={!redirectUrl && !isValidInput} allowAddress={true} />
<SearchChainError
show={(!isValidOrigin || !isValidDestination) && isValidInput && !!multiProvider}
show={
!redirectUrl &&
(!isValidOrigin || !isValidDestination) &&
isValidInput &&
!!multiProvider
}
/>
</Card>
</>
Expand Down
13 changes: 2 additions & 11 deletions src/features/messages/cards/TransactionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ function TransactionCard({
children,
}: PropsWithChildren<{ chainName: string; title: string; helpText: string }>) {
return (
<Card className="flex min-w-[400px] flex-1 basis-0 flex-col space-y-3">
<Card className="flex w-full flex-1 basis-0 flex-col space-y-3 sm:min-w-[400px]">
<div className="flex items-center justify-between">
<div className="relative -left-0.5 -top-px">
<ChainLogo chainName={chainName} />
Expand Down Expand Up @@ -266,6 +266,7 @@ function TransactionDetails({
displayWidth="w-60 sm:w-64"
showCopy={true}
blurValue={blur}
link={txExplorerLink}
/>
<KeyValueRow
label="From:"
Expand Down Expand Up @@ -311,16 +312,6 @@ function TransactionDetails({
blurValue={blur}
/>
)}
{txExplorerLink && (
<a
className={`block ${styles.textLink}`}
href={txExplorerLink}
target="_blank"
rel="noopener noreferrer"
>
View in block explorer
</a>
)}
</>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/features/messages/ica.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ export function useRelatedIcaMessage(
true,
);
}
return buildMessageQuery(MessageIdentifierType.OriginTxHash, originTxHash, 10, true);
return buildMessageQuery(MessageIdentifierType.OriginTxHash, originTxHash, 1000, true);
}, [shouldSearch, originTxHash]);

// Execute the query
Expand Down
39 changes: 39 additions & 0 deletions src/features/messages/queries/useMessageQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,45 @@ export function useMessageQuery({ messageId, pause }: { messageId: string; pause
};
}

/**
* Hook to count messages in a given origin transaction.
* Used to determine if we should show the "View all messages in this transaction" link.
*/
export function useTransactionMessageCount(originTxHash: string | undefined) {
const { scrapedDomains: scrapedChains } = useScrapedDomains();
const multiProvider = useMultiProvider();

// Build query for origin tx hash
const { query, variables } = useMemo(() => {
if (!originTxHash) {
// Return a no-op query
return buildMessageQuery(
MessageIdentifierType.OriginTxHash,
'0x0000000000000000000000000000000000000000000000000000000000000000',
1,
true,
);
}
return buildMessageQuery(MessageIdentifierType.OriginTxHash, originTxHash, 1000, true);
}, [originTxHash]);

// Execute query
const [{ data }] = useQuery<MessagesStubQueryResult>({
query,
variables,
pause: !originTxHash,
});

// Parse results
const messageCount = useMemo(() => {
if (!data || !originTxHash) return 0;
const messages = parseMessageStubResult(multiProvider, scrapedChains, data);
return messages.length;
}, [data, multiProvider, scrapedChains, originTxHash]);

return messageCount;
}

function isWindowVisible() {
return document.visibilityState === 'visible';
}
Loading