feat: add transaction page to view all messages in a tx#261
feat: add transaction page to view all messages in a tx#261yorhodes wants to merge 4 commits intofeat/ica-decodingfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
e40c6f6 to
5764120
Compare
| <SpinnerIcon width={40} height={40} /> | ||
| </div> | ||
| <div className="mt-4 text-center font-light leading-loose text-gray-700"> | ||
| Found it! Redirecting... |
There was a problem hiding this comment.
in practice this text never actually shows up and it just flashes me from the search page to the tx page
| > | ||
| View all {txMessageCount} messages in tx → | ||
| </Link> | ||
| )} |
|
codex review [P1] ICA decode can throw and crash render for valid CALLS messages — src/features/messages/ica.ts:136-138. [P2] Search redirect can send users to an empty /tx page when results only come from PI — src/features/messages/MessageSearch.tsx:145-158 + src/features/transactions/useTransactionMessagesQuery.ts:24-42. [P2] Auto‑redirect triggers even with no user input — src/features/messages/MessageSearch.tsx:145-152. [P3] Tx message count is capped at 10, so the “View all N messages” link can undercount — src/features/messages/queries/useMessageQuery.ts:153-189. [P3] Related COMMITMENT/REVEAL lookup is limited to 10 messages — src/features/messages/ica.ts:823-855. |
1c6b402 to
52cc0d1
Compare
52cc0d1 to
3be8dc2
Compare
- Add /tx/[txHash] page showing all messages dispatched in a transaction - Add auto-redirect from search: single result → message page, tx hash match → tx page - Add 'View all messages in tx' link on message detail page when tx has multiple messages - Add SearchRedirecting state for smoother UX during redirects - Add useTransactionMessageCount hook for efficient message count queries - Make TransactionCard and KeyValueRow more responsive
3be8dc2 to
1411bdb
Compare
- P1: Fix ICA decode crash for payloads >32 bytes by moving zero check inside try block and using safe regex instead of BigNumber.isZero() - P2: Prevent auto-redirect on homepage when latest messages has 1 result - P2: Gate tx page redirect on GraphQL results (not PI) since tx page only queries GraphQL - P3: Increase message count query limit from 10 to 1000 for accurate 'View all N messages' link - P3: Increase related ICA message lookup limit from 10 to 1000 to find related COMMITMENT/REVEAL in large fan-out transactions
| 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}`; | ||
| } | ||
|
|
||
| // 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; | ||
| })(); |
There was a problem hiding this comment.
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
| useEffect(() => { | ||
| if (redirectUrl) { | ||
| router.replace(redirectUrl); | ||
| } | ||
| }, [redirectUrl, router]); |
There was a problem hiding this comment.
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
| useEffect(() => { | ||
| setIsManuallyToggled(false); | ||
| }, [forceExpanded]); |
There was a problem hiding this comment.
nit: move effects to the end (right before the first return)
| const title = useMemo(() => { | ||
| switch (messageType) { | ||
| case 'warp': | ||
| return 'Warp Transfer'; | ||
| case 'ica-commitment': | ||
| return 'Interchain Account Commitment'; | ||
| case 'ica-reveal': | ||
| return 'Interchain Account Reveal'; | ||
| case 'ica-calls': | ||
| return 'Interchain Account Calls'; | ||
| default: | ||
| return 'Message'; | ||
| } | ||
| }, [messageType]); |
There was a problem hiding this comment.
can't this be combined with the summaryLine memo? Ideally wanna avoid usage of hooks if there is no need
| return ( | ||
| <div className="flex items-center gap-1.5 rounded-full bg-green-100 px-2.5 py-1"> | ||
| <Image src={CheckmarkIcon} width={14} height={14} alt="" /> | ||
| <span className="text-xs font-medium text-green-700"> | ||
| Delivered{duration && ` (${duration})`} | ||
| </span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| if (status === MessageStatus.Failing) { | ||
| return ( | ||
| <div className="flex items-center gap-1.5 rounded-full bg-red-100 px-2.5 py-1"> | ||
| <span className="text-xs font-medium text-red-700">Failing</span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex items-center gap-1.5 rounded-full bg-amber-100 px-2.5 py-1"> | ||
| <SpinnerIcon width={14} height={14} color="#b45309" /> | ||
| <span className="text-xs font-medium text-amber-700">{toTitleCase(status)}</span> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Seems that some of these can be made into a reusable component that you can a children too
| if (isFetching && !hasRun) { | ||
| return ( | ||
| <div className="mx-auto max-w-2xl"> | ||
| <Card className="flex min-h-[20rem] items-center justify-center"> | ||
| <div className="flex flex-col items-center gap-4"> | ||
| <SpinnerIcon width={40} height={40} /> | ||
| <p className="text-gray-500">Loading transaction messages...</p> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Error state | ||
| if (isError) { | ||
| return ( | ||
| <div className="mx-auto max-w-2xl"> | ||
| <Card className="flex min-h-[20rem] items-center justify-center"> | ||
| <div className="flex flex-col items-center gap-4 text-center"> | ||
| <p className="text-red-500">Error loading transaction</p> | ||
| <p className="text-sm text-gray-500"> | ||
| Please check the transaction hash and try again. | ||
| </p> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Not found state | ||
| if (hasRun && !isMessagesFound) { | ||
| return ( | ||
| <div className="mx-auto max-w-2xl"> | ||
| <Card className="flex min-h-[20rem] items-center justify-center"> | ||
| <div className="flex flex-col items-center gap-4 text-center"> | ||
| <p className="text-gray-700">No messages found</p> | ||
| <p className="max-w-md text-sm text-gray-500"> | ||
| No Hyperlane messages were found for this transaction hash. The transaction may not | ||
| have dispatched any messages, or it may not be indexed yet. | ||
| </p> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="mx-auto max-w-2xl"> |
| * Hook to query all messages dispatched in a single origin transaction. | ||
| * Returns full Message objects (not stubs) for detailed display. | ||
| */ | ||
| export function useTransactionMessagesQuery(txHash: string) { |
There was a problem hiding this comment.
is this not basically the same hook as message query, can't it not be re-used/modified to fit this feature?
| /* Ensure tooltips appear above the sticky header (z-10) */ | ||
| [role='tooltip'], | ||
| .htw-tooltip { | ||
| z-index: 50 !important; | ||
| } | ||
|
|
There was a problem hiding this comment.
the tooltip component has a prop called tooltipClassName you can use

Summary
Adds a
/tx/[txHash]page that displays all Hyperlane messages dispatched in a single origin transaction. This is useful for multi-message transactions like superswaps (warp transfer + ICA calls).Features
/tx/[txHash]) - Shows origin transaction info and a list of all messages with their status/message/[msgId]/tx/[txHash]Screenshots
Transaction page with multiple messages:
Stacked on #259 (feat/ica-decoding)