Skip to content

feat: add ICA message decoding and visualization#259

Merged
paulbalaji merged 35 commits into
mainfrom
feat/ica-decoding
May 11, 2026
Merged

feat: add ICA message decoding and visualization#259
paulbalaji merged 35 commits into
mainfrom
feat/ica-decoding

Conversation

@yorhodes
Copy link
Copy Markdown
Member

@yorhodes yorhodes commented Jan 28, 2026

Summary

Adds basic ICA message detection, decoding, visualization, and debugger support. This PR is stacked on #261 so ICA details are available both on message pages and inside expanded /tx/[txHash] rows.

Changes

  • Decode ICA message body variants from InterchainAccountMessage.sol:
    • CALLS: owner/ISM/salt + calls array
    • COMMITMENT: owner/ISM/salt + commitment hash
    • REVEAL: ISM + commitment hash
  • Detect ICA messages via registry ICA router addresses, including canonical bytes32 matching for Tron-style addresses
  • Display IcaDetailsCard above message details for ICA messages
  • Show owner, derived ICA account, salt/user, ISM, calls, commitment/reveal status, and related message links
  • Fetch REVEAL calls from destination mailbox.process(metadata, message) tx metadata, including multicall process txs
  • Surface pending REVEAL CCIP Read gateway/ISM information
  • Add debugger support for ICA CALLS failures with explicit non-EVM guard
  • Use destination native decimals for call value formatting

Stack

Validation

  • pnpm run format
  • pnpm run typecheck
  • pnpm run lint

Test messages

  • Delivered REVEAL preview test: 0x36c5352103a884f6fce67c05e4811a37b3c6adc81745edbce8369aa56fb7d3a8
  • Earlier REVEAL: 0x5ba4ee8907dc80572c8bfb782d918d0b82755463a361f2667f44a010ba85900b
  • COMMITMENT: 0xe89b32c5f63f0f62ffea080f3050d4a2acb9e5e39ac21b2ed3573a1c0c41ec1d

@yorhodes yorhodes requested a review from Xaroz as a code owner January 28, 2026 21:39
@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 May 11, 2026 3:36pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Debugger and UI were updated to treat ICA messages as structured entities: decoding ICA bodies, deriving ICA addresses from registry routers, extracting REVEAL/COMMITMENT details and per-call payloads, running inter-call gas/value checks, and surfacing structured debug results to an enhanced ICA details card with cross-chain lookups.

Changes

ICA Debugging Infrastructure

Layer / File(s) Summary
Data Shape
src/features/debugger/types.ts
MessageDebugResult gains optional icaDetails?: { failedCallIndex: number; totalCalls: number; errorReason: string }.
Core Implementation
src/features/debugger/debugMessage.ts
tryDebugIcaMsg refactored to return `Promise<IcaDebugResult

ICA Message Types and Utilities

Layer / File(s) Summary
Data Shape / Types
src/types.ts, src/features/messages/ica.ts
Adds IcaCall interface (to,value,data), IcaMessageType enum, and DecodedIcaMessage shape for CALLS/COMMITMENT/REVEAL decoding.
Decoding / Parsing
src/features/messages/ica.ts
decodeIcaBody and parseIcaMessageDetails implement full ICA body parsing (CALLS ABI decode, REVEAL/COMMITMENT metadata extraction).
Address Derivation
src/features/messages/ica.ts
Adds registry-based router map, getIcaRouterAddress, and computeIcaAddress which queries destination router/contract to derive the interchain account.
Reveal / Multicall Extraction
src/features/messages/ica.ts
decodeRevealMetadata, multicall decoding strategies, and fetchRevealCalls locate mailbox.process invocations (including Multicall variants) and return decoded IcaCall[].
React Hooks / Wiring
src/features/messages/ica.ts
Adds useIcaAddress, useRevealCalls, useCcipReadIsmUrls, useRelatedIcaMessage, and router/address utilities for UI consumption; removes legacy tryFetchIcaAddress/ICA_ADDRESS usage.

ICA UI Components

Layer / File(s) Summary
Props / Data Flow
src/features/messages/MessageDetailsInner.tsx, src/features/messages/cards/IcaDetailsCard.tsx
MessageDetailsInner uses useIsIcaMessage({ sender, recipient }) and passes debugResult into IcaDetailsCard; IcaDetailsCard props now accept debugResult?: MessageDebugResult.
Decoding & Cross-Message Lookup
src/features/messages/cards/IcaDetailsCard.tsx
Rewires card to call decodeIcaBody, build messageBytes, derive owner/ISM/salt from related messages when applicable, compute derived ICA address via useIcaAddress, and fetch reveal calls via useRevealCalls / useRelatedIcaMessage.
UI Rendering
src/features/messages/cards/IcaDetailsCard.tsx
Replaces static calls UI with message-type labels, commitment/reveal status panels, CCIP Read ISM gateway section, owner/account/ISM/salt rows, salt→user address extraction, and per-call IcaCallDetails with failed-call highlighting driven by debugResult.failedCallIndex.
Minor Layout Fix
src/features/messages/cards/KeyValueRow.tsx
Ensures LinkIcon anchor has shrink-0 class to preserve icon sizing.

Sequence Diagram(s)

sequenceDiagram
    participant App as Message Layer
    participant Debugger as Debug Processor
    participant ICA as ICA Utilities
    participant Chain as Destination Chain
    participant UI as IcaDetailsCard

    App->>Debugger: Send message for debugging
    Debugger->>ICA: decodeIcaBody(body)
    ICA-->>Debugger: DecodedIcaMessage (CALLS/REVEAL/COMMITMENT)
    Debugger->>ICA: computeIcaAddress(owner, ism, salt)
    ICA->>Chain: Call router/contract to derive account
    Chain-->>ICA: Derived ICA address
    ICA-->>Debugger: Address result
    Debugger->>Debugger: tryCheckIcaCall(each call with value)
    Debugger-->>App: IcaDebugResult (or null) with icaDetails on failure
    App->>UI: Render message + debugResult
    UI->>ICA: useRelatedIcaMessage / useRevealCalls (if REVEAL)
    ICA->>Chain: Fetch destination tx / router routes / ISM urls
    Chain-->>ICA: Reveal calls, ISM urls, related message data
    ICA-->>UI: Calls + gateway URLs
    UI-->>App: Display enriched ICA details and failed-call highlight
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: adding ICA message decoding and visualization support. It's concise and reflects the core changes across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly outlines ICA message detection, decoding, visualization, and debugger support with specific changes across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@src/features/debugger/debugMessage.ts`:
- Around line 418-426: The loop in debugMessage.ts calls tryCheckIcaCall using
recipient (the ICA router) which leads to misleading gas estimates; instead,
skip ICA call checks until the actual derived ICA address is available—update
the loop that iterates over calls to detect ICA-targeted calls (using recipient
and call.to context) and bypass calling tryCheckIcaCall for those, emitting a
clear debug/info message like "Skipping ICA call check until derived ICA address
is implemented" rather than performing estimateGas with the router address; keep
tryCheckIcaCall intact for future use once proper derivation is added.

In `@src/features/messages/cards/IcaDetailsCard.tsx`:
- Around line 612-613: Wrap the BigNumber conversion for call.value in defensive
handling: validate or try-catch around BigNumber.from(call.value) used to
compute hasValue and formattedValue, and fall back to safe defaults (hasValue =
false and formattedValue = '0') when parsing fails; update the logic in the
block that defines hasValue and formattedValue (referencing BigNumber.from,
call.value, hasValue, formattedValue, and fromWei) so malformed inputs don't
throw and the component can render safely.

In `@src/features/messages/ica.ts`:
- Around line 559-566: The current fallback in the message-matching logic
decodes reveal metadata from processCalls[0] and returns revealData.calls, which
can return data for the wrong message when multiple messages are batched; change
the fallback to return null instead of decoding/returning processCalls[0] so
callers receive a clear "unavailable" signal (update the code path that
references decodeRevealMetadata, processCalls, and revealData to return null
here and ensure callers handle a null result appropriately).
- Around line 136-138: The empty-body/malformed input check in decodeIcaBody is
unsafe because BigNumber.from(body) will throw on non-numeric or non-hex
strings; wrap the conversion in a safe validation or try/catch so malformed
input returns null instead of crashing. Specifically, in decodeIcaBody validate
that body is a non-empty numeric/hex string (or call ethers/utils.isHexString or
similar) before calling BigNumber.from(body), or catch errors from
BigNumber.from and return null; update the logic around BigNumber.from(body) to
perform this safe check so decodeIcaBody never throws on malformed body values.

In `@src/store.ts`:
- Around line 44-45: The store exposes icaRouterAddressMap and
setIcaRouterAddressMap but they are never populated or read; remove the unused
slice to avoid dead state. Delete the icaRouterAddressMap property and
setIcaRouterAddressMap setter from the store type and initialization (references
to icaRouterAddressMap and setIcaRouterAddressMap in store creation), and remove
any tests/consumers that reference them; alternatively, if you prefer
centralizing state, refactor callers that currently read the module-level
ICA_ROUTER_MAP (from buildIcaRouterAddressMap / ICA_ROUTER_MAP) to instead read
from and update the store via setIcaRouterAddressMap and
icaRouterAddressMap—pick one approach and apply consistently so only one source
of truth remains.

In `@src/types.ts`:
- Around line 75-91: Remove the unused IcaMessageDetails type declaration and
its export: delete the IcaMessageDetails interface (keeping IcaCall and
IcaRouterAddressMap intact), then search for any lingering imports/usages and
replace them with the actual DecodedIcaMessage type returned by
decodeIcaBody/parseIcaMessageDetails where appropriate; ensure no other modules
reference IcaMessageDetails and run the typecheck/build to confirm no remaining
references.
🧹 Nitpick comments (3)
src/features/messages/cards/IcaDetailsCard.tsx (2)

143-210: Async explorer URL fetching could be simplified

This callback recreates on every render when dependencies change, and the effect triggers the fetch. It's like walkin' through the swamp the long way. Consider using useQuery from tanstack (already imported in ica.ts) for consistency with other data fetching in this PR, or at minimum add cleanup to prevent setting state on unmounted components.

♻️ Consider adding cleanup for unmount safety
   useEffect(() => {
-    getExplorerUrls().catch(() => setExplorerUrls({}));
-  }, [getExplorerUrls]);
+    let cancelled = false;
+    getExplorerUrls()
+      .then((urls) => {
+        if (!cancelled) setExplorerUrls(urls || {});
+      })
+      .catch(() => {
+        if (!cancelled) setExplorerUrls({});
+      });
+    return () => {
+      cancelled = true;
+    };
+  }, [getExplorerUrls]);

This would require modifying getExplorerUrls to return the urls object instead of setting state directly.


513-527: IIFE pattern works but extraction would be cleaner

This works fine, but usin' an IIFE inside JSX is a bit like buildin' a house in a swamp - technically possible but makes folks scratch their heads. Could extract to a tiny component or move the logic above the return.

src/features/messages/ica.ts (1)

494-499: Avoid any type for multiProvider

Usin' any here is like leavin' the door to my swamp wide open - anything could wander in. The MultiProtocolProvider type is already imported in the store and used elsewhere.

♻️ Proposed type fix
+import { MultiProtocolProvider } from '@hyperlane-xyz/sdk';
+
 export async function fetchRevealCalls(
   destinationChainName: string,
   processTxHash: string,
   messageId: string,
-  multiProvider: any,
+  multiProvider: MultiProtocolProvider,
 ): Promise<IcaCall[] | null> {

Comment thread src/features/debugger/debugMessage.ts
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/ica.ts
Comment thread src/features/messages/ica.ts
Comment thread src/store.ts Outdated
Comment thread src/types.ts Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx
Comment thread src/features/debugger/debugMessage.ts Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/cards/IcaDetailsCard.tsx Outdated
Comment thread src/features/messages/ica.ts
Comment thread src/features/messages/ica.ts
Comment thread src/features/messages/ica.ts
- 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
paulbalaji and others added 12 commits May 11, 2026 16:19
Add support for decoding and displaying Interchain Account (ICA) messages:

- Decode all 3 ICA message types: CALLS, COMMITMENT, REVEAL
- Detect ICA messages by checking sender/recipient against known ICA routers
- Compute derived ICA addresses using the SDK
- Fetch CCIP Read ISM URLs for REVEAL messages
- Link related COMMITMENT and REVEAL messages bidirectionally
- Add IcaDetailsCard component with:
  - Status sections (delivered/pending) with commitment hash
  - Owner, Account, ISM, Salt, User fields
  - CCIP Read Gateway info for pending REVEAL messages
  - Call details with execution status
- Update debugger to use new ICA decoding API
- Fix P1: Wrap BigNumber.from() in try/catch to prevent crashes on invalid data
- Fix P2: Remove risky fallback to first process call, return null instead
- Cleanup: Remove unused IcaMessageDetails type and icaRouterAddressMap store
- UI: Fix link icon alignment in KeyValueRow
- Add computeIcaAddress() to get real ICA address for accurate debugging
- Include call.value in gas estimation for ICA calls
- Add failed call indicator (red/amber) in IcaDetailsCard UI
- Return structured IcaDebugResult with failedCallIndex from debugger
Why: useComputeIcaAddress, useRevealCalls, and useCcipReadIsmUrls were
gated on `!== ProtocolType.Ethereum`, so ICA messages to Tron skipped
address derivation, reveal-call fetching, and CCIP-Read ISM lookup even
though Tron is EVM-compatible. Switched to `isEVMLike` to match the
helper already used by warp-fee fetching.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: isAddressIcaRouter compared lowercased strings against the registry
map, but Tron messages have their recipient decoded to base58 (T...)
while registry entries are EVM-style 0x hex. The strings never matched,
so isIcaMessage returned false and IcaDetailsCard never rendered for
Arbitrum -> Tron messages. Canonicalize both sides to bytes32 hex,
matching the warp-route Tron fix in #321.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ails

Why: ICA messages need to show their decoded contents front and centre
just like warp routes do. Removed the show/hide toggle (the card was
just collapsed by default), aligned the header with the
WarpRouteIsmDetailsCard / WarpRouteVisualizationCard pattern (no gray
header bar, primary-800 title, no out-of-theme blue toggle link), and
moved the card to render right after the timeline, ahead of message
content / gas / ISM details.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the Message Details / Gas Details / Content Details visual
treatment (gray header bar with purple dot + gray title) instead of the
ad-hoc primary-800 heading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- getFormattedCallValue now takes nativeDecimals and pulls them from
  the destination chain's nativeToken (falls back to 18). Hardcoded 18
  was wrong for Tron native (6 decimals) and any other non-EVM
  destination ICA might support.
- tryDecodeMulticall takes the mailbox address and skips inner calls
  whose target isn't the mailbox before parsing, so heterogeneous
  multicall payloads can't accidentally decode non-process calldata via
  selector collisions.
- Replaced string-concat classNames in IcaCallDetails with clsx() to
  match the repo standard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop rounded-md (3px) for default rounded (2px) on status/call panels
- Replace vibrant bg-green-50 (#00C467) with muted bg-green-100/40 so the
  Done state matches the visual weight of the amber Pending state

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/features/messages/ica.ts
Comment thread src/features/messages/ica.ts
Base automatically changed from feat/tx-page to main May 11, 2026 15:34
@paulbalaji paulbalaji enabled auto-merge (squash) May 11, 2026 15:36
@paulbalaji paulbalaji merged commit b2ce701 into main May 11, 2026
12 checks passed
@paulbalaji paulbalaji deleted the feat/ica-decoding branch May 11, 2026 15:37
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.

3 participants