Skip to content

feat: add transaction page to view all messages in a tx#261

Open
yorhodes wants to merge 44 commits intofeat/ica-decodingfrom
feat/tx-page
Open

feat: add transaction page to view all messages in a tx#261
yorhodes wants to merge 44 commits intofeat/ica-decodingfrom
feat/tx-page

Conversation

@yorhodes
Copy link
Copy Markdown
Member

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

  • Transaction page (/tx/[txHash]) - Shows origin transaction info and a list of all messages with their status
  • Auto-redirect from search - When searching for a message ID or tx hash:
    • Single result → redirects to /message/[msgId]
    • Multiple results matching origin tx hash → redirects to /tx/[txHash]
  • "View all messages" link - Message detail page shows a link when the transaction contains multiple messages
  • Smooth redirect UX - Shows "Found it! Redirecting..." state instead of flashing results

Screenshots

Transaction page with multiple messages:

  • Shows origin chain, tx hash, sender, mailbox, time, and block
  • Lists each message with type detection (Warp Transfer, ICA Commitment, ICA Reveal, ICA Calls)
  • Displays delivery status and duration for each message

Stacked on #259 (feat/ica-decoding)

@yorhodes yorhodes requested a review from Xaroz as a code owner January 28, 2026 23:46
@vercel
Copy link
Copy Markdown

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperlane-explorer Ready Ready Preview, Comment Apr 13, 2026 4:58pm

Request Review

<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

Comment on lines +118 to +121
>
View all {txMessageCount} messages in tx →
</Link>
)}
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

Comment thread src/features/messages/queries/useMessageQuery.ts Outdated
Comment thread src/features/messages/MessageSearch.tsx
Comment thread src/features/transactions/TransactionDetails.tsx
Comment thread src/features/transactions/MessageSummaryRow.tsx Outdated
@paulbalaji
Copy link
Copy Markdown
Collaborator

codex review

[P1] ICA decode can throw and crash render for valid CALLS messages — src/features/messages/ica.ts:136-138.
BigNumber.from(body).isZero() throws for payloads > 32 bytes (common for ICA CALLS) and it runs outside the try/catch, so any such message blows up IcaDetailsCard, MessageSummaryRow, and debugMessage. Suggest moving the zero check inside the try and using a safe hex check (e.g. const hex = strip0x(body); if (!hex || /^0*$/.test(hex)) return null;).

[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.
The redirect uses messageListResult (PI if GraphQL is empty), but the /tx page only queries GraphQL. If PI returns multiple messages matching the origin tx hash, you’ll redirect to a tx page that shows “No messages found.” Gate the tx redirect on isMessagesFound (GraphQL) or add PI support to the tx view.

[P2] Auto‑redirect triggers even with no user input — src/features/messages/MessageSearch.tsx:145-152.
When the default “latest messages” view has exactly one result, the page navigates away immediately. Consider requiring hasInput (or a valid hash match) before auto‑redirecting.

[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.
If a transaction has >10 messages, the UI shows “View all 10…” even though there are more. Consider a count‑only query or a higher limit (or change the copy to avoid implying a full count).

[P3] Related COMMITMENT/REVEAL lookup is limited to 10 messages — src/features/messages/ica.ts:823-855.
In large fan‑out txs, the related ICA message can be missed, showing incorrect “pending” status and missing cross‑links. Raise the limit or query by commitment hash instead of scanning a capped list.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- 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
- 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
Comment on lines +137 to +166
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;
})();
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

Comment on lines +169 to +173
useEffect(() => {
if (redirectUrl) {
router.replace(redirectUrl);
}
}, [redirectUrl, router]);
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

Comment on lines +42 to +44
useEffect(() => {
setIsManuallyToggled(false);
}, [forceExpanded]);
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.

nit: move effects to the end (right before the first return)

Comment on lines +121 to +134
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]);
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.

can't this be combined with the summaryLine memo? Ideally wanna avoid usage of hooks if there is no need

Comment on lines +211 to +235
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>
);
}
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.

Seems that some of these can be made into a reusable component that you can a children too

Comment on lines +21 to +68
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">
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.

same for these

yorhodes and others added 26 commits February 25, 2026 13:42
Co-authored-by: pbio <10051819+paulbalaji@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: hyper-gonk[bot] <246310972+hyper-gonk[bot]@users.noreply.github.com>
Co-authored-by: hyper-gonk[bot] <246310972+hyper-gonk[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: hyper-gonk[bot] <246310972+hyper-gonk[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: hyper-gonk[bot] <246310972+hyper-gonk[bot]@users.noreply.github.com>
…308)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: hyper-gonk[bot] <246310972+hyper-gonk[bot]@users.noreply.github.com>
Co-authored-by: Jason Guo <33064781+Xaroz@users.noreply.github.com>
# Conflicts:
#	src/features/debugger/debugMessage.ts
#	src/features/messages/MessageDetails.tsx
#	src/features/messages/MessageSearch.tsx
#	src/features/messages/cards/IcaDetailsCard.tsx
#	src/features/messages/cards/KeyValueRow.tsx
#	src/features/messages/cards/TransactionCard.tsx
#	src/features/messages/ica.ts
#	src/types.ts
@socket-security
Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @hyperlane-xyz/core is 70.0% likely obfuscated

Confidence: 0.70

Location: Package overview

From: package.jsonnpm/@hyperlane-xyz/core@10.2.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@hyperlane-xyz/core@10.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: pnpm-lock.yamlnpm/entities@6.0.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@6.0.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@paulbalaji
Copy link
Copy Markdown
Collaborator

i think the pr should be targetting main instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants