Skip to content

do not merge: Nambrot/crossstableswap demo#884

Open
nambrot wants to merge 64 commits into
mainfrom
nambrot/crossstableswap-demo
Open

do not merge: Nambrot/crossstableswap demo#884
nambrot wants to merge 64 commits into
mainfrom
nambrot/crossstableswap-demo

Conversation

@nambrot
Copy link
Copy Markdown
Collaborator

@nambrot nambrot commented Jan 12, 2026

Summary by CodeRabbit

  • New Features

    • Unified token/chain selector and searchable token lists, mobile quick-select; wallet dropdown with recipient-address modal; many new icons and a reusable modal header.
  • Refactor

    • Token handling moved from index-based to key-based; routing, fee and transfer flows reworked; legacy selection modals removed and navigation centralized.
  • Style

    • Overhauled theme (fonts, colors, gradients), updated buttons, banners, scrollbars, responsive header/footer and layout tweaks.
  • Bug Fixes

    • Improved submit button behavior for wallet connection and various positioning/disabled-state fixes.
  • Chores

    • Dependency bumps, added /public/fonts to ignore, refreshed config and chain/token data.

Xaroz and others added 30 commits December 5, 2025 13:36
chore: change token identification to use an identifier instead of tokenIndex
paulbalaji and others added 11 commits January 6, 2026 17:04
## Summary
Extracts generic UI fixes from #877 that can be applied to the
rebranding branch:

- **Remove rounded corners**: Removed rounded-t-md from wallet sidebar
header for cleaner look
- **Smaller token selectors**: Reduced size of send/receive token
selector buttons for a more compact UI
- **Page centralization**: Vertically center the transfer form with
responsive tip card layout
- **Warning banner styling**: Added rounded corners, improved spacing
and alignment with tip card
- **Favicon & app icons**: Updated all favicon and app icon files with
new branding

## Test plan
- [ ] Check sidebar header has no rounded corners
- [ ] Confirm token selectors are visually smaller
- [ ] Test page layout is vertically centered on various screen sizes
- [ ] Check warning banner has rounded corners and proper spacing from
tip card
- [ ] Verify new favicons and app icons display correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- greptile_comment -->

<h3>Greptile Summary</h3>


This PR extracts clean UI refinements from #877, focusing on making the
interface more compact and polished. The changes reduce padding
throughout the transfer form (from `p-4` to `p-3`), shrink token
selector icons (44px → 36px), and tighten spacing across components. The
page layout now properly centers vertically with `items-center` on the
container, and the warning banner gained rounded corners for visual
consistency.

**Key improvements:**
- Compact token selectors with smaller icons and tighter spacing
- Vertically centered transfer form improves visual balance
- Warning banner styling matches tip card with rounded corners  
- Sidebar header cleaned up (removed `rounded-t-md`)
- Escape key closes sidebar (proper cleanup prevents memory leaks)
- Responsive tip card layout with better positioning

The Escape key implementation is well done - it properly cleans up the
event listener and only attaches when the sidebar is open. All spacing
adjustments are consistent and maintain proper visual hierarchy.

<details><summary><h3>Confidence Score: 5/5</h3></summary>


- This PR is safe to merge with no risk
- All changes are purely visual styling adjustments (Tailwind classes)
with no logic modifications. The Escape key handler is properly
implemented with cleanup. No security concerns or breaking changes.
- No files require special attention
</details>


<details><summary><h3>Important Files Changed</h3></summary>




| Filename | Overview |
|----------|----------|
| src/components/layout/AppLayout.tsx | Vertically centered main content
area by adding `items-center` and changing `mt-4` to `my-4` |
| src/features/transfer/TransferTokenForm.tsx | Reduced spacing
throughout form: smaller gaps, tighter padding, smaller font sizes for
compact view |
| src/features/wallet/SideBarMenu.tsx | Removed `rounded-t-md` from
header and added Escape key listener for closing sidebar |
| src/pages/index.tsx | Restructured layout for responsive tip card
positioning with cleaner flex/gap layout |

</details>


</details>


<details><summary><h3>Sequence Diagram</h3></summary>

```mermaid
sequenceDiagram
    participant User
    participant Page as index.tsx
    participant Layout as AppLayout
    participant Form as TransferTokenForm
    participant TokenSelect as TokenSelectField
    participant Sidebar as SideBarMenu
    participant Banner as WarningBanner

    User->>Page: Visits app
    Page->>Layout: Renders with centered layout
    Layout->>Form: Displays transfer form (vertically centered)
    Layout->>Banner: Shows warning banner (with rounded corners)
    
    User->>TokenSelect: Clicks token selector
    TokenSelect->>TokenSelect: Opens modal (compact 36px icons)
    
    User->>Sidebar: Opens wallet sidebar
    Sidebar->>Sidebar: Displays without rounded header
    User->>Sidebar: Presses Escape key
    Sidebar->>Sidebar: Closes sidebar
    
    User->>Form: Enters amount in compact input
    Form->>Form: Validates with tighter spacing (p-3)
    User->>Form: Clicks swap button (8x8px)
    Form->>Form: Swaps origin/destination
```
</details>


<!-- greptile_other_comments_section -->

<!-- /greptile_comment -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…selector

feat: Unified Chain Token Selector Modal
- Add isStableSwapRoute() helper to detect stableswap token pairs
- Pass destinationToken to WarpCore for stableswap transfers
- Pass destinationToken for stableswap fee estimation
- Set warpRouteWhitelist to empty to use local config
@nambrot nambrot requested a review from Xaroz as a code owner January 12, 2026 20:37
@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 12, 2026

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

Project Deployment Actions Updated (UTC)
hyperlane-warp-template Ready Ready Preview, Comment Feb 5, 2026 8:07pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics-test Ignored Ignored Feb 5, 2026 8:07pm
injective-bridge Ignored Ignored Feb 5, 2026 8:07pm
nexus-bridge Ignored Ignored Feb 5, 2026 8:07pm
ousdt-bridge Ignored Ignored Feb 5, 2026 8:07pm
trump-bridge Ignored Ignored Feb 5, 2026 8:07pm

Request Review

@nambrot nambrot changed the title Nambrot/crossstableswap demo do not merge: Nambrot/crossstableswap demo Jan 12, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 12, 2026

Warning

Rate limit exceeded

@nambrot has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Migrates token handling from index-based to token-key identity, adds unified token/chain selection UI and route-aware token/collateral utilities, refactors transfer/fee/max logic for route selection, adds many new icons/components, overhauls styling/tailwind, and updates config/constants and store to expose tokens and collateralGroups.

Changes

Cohort / File(s) Summary
Icons
src/components/icons/... (BookIcon.tsx, ChevronLargeIcon.tsx, HamburgerIcon.tsx, HyperlaneGradientLogo.tsx, HyperlaneTransparentLogo.tsx, StakeIcon.tsx, SwapIcon.tsx, WebSimpleIcon.tsx, XIcon.tsx)
Added multiple memoized SVG icon components with color prop support.
Token core: utils & hooks
src/features/tokens/utils.ts, src/features/tokens/hooks.ts, src/features/tokens/types.ts
Large refactor introducing token-key identity, token dedupe/build routines, collateralGroups, route checks (findRouteToken, checkTokenHasRoute, isStableSwapRoute), and new token hooks (useTokens, useWarpCoreTokens, useCollateralGroups, getTokenByKey, getInitialTokenKeys).
Token selection UI & panels
src/features/tokens/... (UnifiedTokenChainModal.tsx, TokenListPanel.tsx, TokenList.tsx, ImportTokenButton.tsx, TokenChainIcon.tsx, SelectOrInputTokenIds.tsx, SelectTokenIdField.tsx)
Added unified modal, panels, token list and icons; removed legacy TokenListModal; simplified select/input props; added import token button and TokenChainIcon.
Transfer flow, fees & hooks
src/features/transfer/* (TransferTokenForm.tsx, useFeeQuotes.ts, useTokenTransfer.ts, fees.ts, maxAmount.ts, types.ts, tests)`
Refactor to originTokenKey/destinationTokenKey, route-aware token resolution via findRouteToken/isStableSwapRoute, updated fee/max logic and transfer execution, and TransferForm types; tests adjusted.
Store & analytics
src/features/store.ts, src/features/analytics/utils.ts
Store exposes tokens, collateralGroups and tip-card flag; analytics updated to use token keys and added skipped error strings.
Chain selection UI
src/features/chains/... (ChainFilterPanel.tsx, ChainList.tsx, MobileChainQuickSelect.tsx) and removed files src/features/chains/ChainSelectField.tsx, src/features/chains/ChainSelectModal.tsx
Added chain filter/list/quick-select components; removed old chain selection modal/field and simplified ChainConnectionWarning.
Wallet & recipient UX
src/features/wallet/... (WalletDropdown.tsx, RecipientAddressModal.tsx, ConnectWalletButton.tsx, SideBarMenu.tsx)
New WalletDropdown and RecipientAddressModal; ConnectWalletButton styling tweaked; SideBarMenu adds Escape-to-close and UX refinements.
Header / Nav / Footer / layout
src/components/nav/Nav.tsx, src/components/nav/Header.tsx, src/components/nav/Footer.tsx, src/pages/index.tsx, removed src/components/nav/FloatingButtonStrip.tsx
Added Nav exports and NavItem; reworked header/footer responsive layout; removed floating button strip; adjusted home layout.
UI primitives & components
src/components/input/SearchInput.tsx, src/components/layout/ModalHeader.tsx, src/components/tip/TipCard.tsx, src/components/banner/*, src/components/buttons/*, src/components/input/TextField.tsx
Added SearchInput and ModalHeader; redesigned TipCard; visual updates to banners and buttons; adjusted TextField spacing and ConnectAwareSubmitButton disabled logic.
Transfer UI & modals
src/features/transfer/* (TransferSection.tsx, TransferFeeModal.tsx, RecipientConfirmationModal.tsx, TransfersDetailsModal.tsx, FeeSectionButton.tsx, TransferTokenCard.tsx)
Added TransferSection; migrated modals to ModalHeader; fee UI and loading placeholders adjusted; recipient resolution updated.
Styling / fonts / tailwind
tailwind.config.js, src/styles/globals.css, src/pages/_app.tsx, src/pages/_document.tsx, src/consts/app.ts
Overhauled Tailwind theme (fonts, colors, shadows, gradients), added custom font-face and scrollbar styles, removed MAIN_FONT usage, updated background constants.
Constants & data
src/consts/args.ts, src/consts/config.ts, src/consts/links.ts, src/consts/warpRouteWhitelist.ts, src/consts/chains.yaml, src/consts/warpRoutes.yaml
Query params switched to ORIGIN_TOKEN/DESTINATION_TOKEN; config defaults moved to token keys and enableExplorerLink enabled; added stake link; warpRouteWhitelist default set to a specific route; chains and warpRoutes populated.
Misc & deps
.gitignore, package.json
Ignored /public/fonts; bumped @hyperlane-xyz/sdk and @hyperlane-xyz/utils to 20.2.0-stableswap pre-release versions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TokenField as TokenSelectField
    participant Modal as UnifiedTokenChainModal
    participant ChainPanel as ChainFilterPanel
    participant TokenPanel as TokenListPanel
    User->>TokenField: open selector
    TokenField->>Modal: openModal()
    Modal->>ChainPanel: init callbacks
    Modal->>TokenPanel: init callbacks
    alt user filters chains
        User->>ChainPanel: select chain
        ChainPanel->>Modal: selectedChain
        Modal->>TokenPanel: apply chainFilter
    end
    alt user searches tokens
        User->>TokenPanel: type query
        TokenPanel->>TokenPanel: filter & sort (uses collateralGroups)
        TokenPanel->>User: display results
    end
    User->>TokenPanel: choose token
    TokenPanel->>Modal: onSelect(token)
    Modal->>TokenField: return token key
    TokenField->>User: update form originTokenKey/destinationTokenKey
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Xaroz
  • antigremlin
  • xeno097

Poem

In swampy code where tokens roam,
Keys replace the numbered home.
Panels open, routes align,
Icons gleam and styles refine.
Quiet magic — builds look fine.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'do not merge: Nambrot/crossstableswap demo' is partially related to the changeset - it references a cross-stable-swap feature that is present in the code changes, but it's marked as 'do not merge' which suggests this is a work-in-progress or experimental branch.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nambrot/crossstableswap-demo

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: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/features/chains/ChainConnectionWarning.tsx (1)

13-14: Missing ChainName type import, mate.

The ChainName type is used in the component props but it's not imported anywhere in this file. This'll give ye a TypeScript error faster than you can say "get out of me swamp."

🐛 Proposed fix
-import { ChainMetadata, isRpcHealthy } from '@hyperlane-xyz/sdk';
+import { ChainMetadata, ChainName, isRpcHealthy } from '@hyperlane-xyz/sdk';
src/features/transfer/useTokenTransfer.ts (1)

268-280: Potential undefined variable in error path.

If an error is thrown before origin is assigned (e.g., at line 121 or 133), the error handler at line 276 will reference an undefined origin variable. This could cause a secondary error or confusing message.

🐛 Suggested fix
    } else if (
      errorDetails.includes(TRANSFER_TIMEOUT_ERROR1) ||
      errorDetails.includes(TRANSFER_TIMEOUT_ERROR2)
    ) {
+     const chainName = origin ?? 'the origin chain';
      toast.error(
-       `Transaction timed out, ${getChainDisplayName(multiProvider, origin)} may be busy. Please try again.`,
+       `Transaction timed out, ${origin ? getChainDisplayName(multiProvider, origin) : 'the origin chain'} may be busy. Please try again.`,
      );
    }
src/features/tokens/balances.ts (1)

13-13: Add missing type imports for ChainName and Address.

The useBalance function signature uses ChainName and Address types (line 13), but they're not imported. These types need to be brought in, likely from @hyperlane-xyz/sdk where IToken and MultiProtocolProvider come from.

src/features/transfer/TransfersDetailsModal.tsx (1)

48-60: TransferProperty value typing/guards look unsafe with transfer || {}.
If any of sender/recipient/msgId/... are missing, CopyButton and rendering can get fed undefined while TransferProperty claims value: string. Better to guard at the callsite or widen the prop type.

Possible fix (widen + guard)
-function TransferProperty({ name, value, url }: { name: string; value: string; url?: string }) {
+function TransferProperty({
+  name,
+  value,
+  url,
+}: {
+  name: string;
+  value?: string;
+  url?: string;
+}) {
   return (
     <div>
       <div className="flex items-center justify-between">
         <label className="text-xs leading-normal tracking-wider text-gray-350">{name}</label>
         <div className="flex items-center space-x-2">
           {url && (
             <a href={url} target="_blank" rel="noopener noreferrer">
               <Image src={LinkIcon} width={14} height={14} alt="" />
             </a>
           )}
-          <CopyButton copyValue={value} width={14} height={14} className="opacity-40" />
+          <CopyButton copyValue={value ?? ''} width={14} height={14} className="opacity-40" />
         </div>
       </div>
       <div className="mt-1 truncate text-xs leading-normal tracking-wider text-gray-900">
-        {value}
+        {value ?? ''}
       </div>
     </div>
   );
 }

Also applies to: 167-196, 247-266

src/features/transfer/useFeeQuotes.ts (1)

47-55: Query key includes an unstable Promise object—this'll cause cache misses and refetches.

The senderPubKey destructured from getAccountAddressAndPubKey() is typed as Promise<HexString> (see line 81), not a stable string. Throwing a Promise directly into the query key means it changes identity on every render, so React Query can't recognize the cached result and refetches unnecessarily each time.

Either await the Promise before adding it to the key, derive a stable identifier from it, or remove it from the key entirely if you're already passing it to queryFn.

🤖 Fix all issues with AI agents
In @package.json:
- Around line 24-26: The package.json currently pins @hyperlane-xyz/sdk and
@hyperlane-xyz/utils to pre-release stableswap builds with commit hashes and
leaves @hyperlane-xyz/widgets at 20.1.0; revert those two entries to the
published stable versions (remove the "-stableswap.*" pins and commit hashes)
and align all three @hyperlane-xyz/* packages to the same published version
(e.g., set @hyperlane-xyz/sdk, @hyperlane-xyz/utils, and @hyperlane-xyz/widgets
to the current stable release) so no pre-release or commit-hash pins land on
main.

In @src/components/input/TextField.tsx:
- Around line 32-33: The default TextField styling removed padding and text-size
which leaves callers like SelectOrInputTokenIds (className="w-full"),
TransferTokenForm (uses text-xl but no padding), and SearchInput (uses invalid
"all:py-2") rendering cramped; restore sensible defaults by updating the
defaultClassName constant in TextField.tsx to include padding (e.g., px-3 py-2)
and a base text-size (e.g., text-sm or text-base), and then fix usages: either
remove redundant sizing/padding from callers or add explicit padding/text
classes where needed (update TransferTokenForm to add px/py if it wants
text-xl), and replace the invalid "all:py-2" in SearchInput with a valid
Tailwind class like "py-2".

In @src/consts/args.ts:
- Around line 3-8: The change removed the legacy single "token" query parameter
and breaks old deep links; update the queryParams parsing logic to detect a
legacy "token" param and map it into the new keys
(WARP_QUERY_PARAMS.ORIGIN_TOKEN and WARP_QUERY_PARAMS.DESTINATION_TOKEN) when
those new params are not present; modify the queryParams utility to prefer
explicit originToken/destinationToken but fall back to populating both from
"token" for backward compatibility, and update related tests to cover legacy and
new-param behaviors.

In @src/consts/warpRouteWhitelist.ts:
- Line 5: The warpRouteWhitelist constant was changed to an empty array which is
truthy and causes warpCoreConfig.ts to call filterToIds() and yield zero routes;
revert warpRouteWhitelist back to null to mean "include everything" or replace
the empty array with explicit route IDs from warpRoutes.yaml (e.g., the
Arbitrum/Base USDC/USDT and Soneium IDs) so filterToIds() returns the intended
subset; locate the warpRouteWhitelist export and update its value accordingly
and verify behavior in warpCoreConfig.ts and any calls to filterToIds().

In @src/features/chains/ChainFilterPanel.tsx:
- Around line 5-9: The interface ChainFilterPanelProps references the type
ChainName but the file is missing its import; add an import for the ChainName
type (the same exported symbol used elsewhere in the project) at the top of the
file so TypeScript recognizes ChainName and the props signature for
ChainFilterPanelProps compiles correctly.

In @src/features/chains/ChainList.tsx:
- Around line 5-9: The ChainListProps interface in ChainList.tsx references the
ChainName type but there is no import; add an import for the ChainName type at
the top of ChainList.tsx (the same module/source used elsewhere in the repo
where ChainName is defined) so the interface compiles and type-checks; ensure
the import uses a type-only import if your bundler/TS config prefers (e.g.,
`import type { ChainName } from '...'`) and keep the symbol name ChainName to
match the interface.

In @src/features/tokens/hooks.ts:
- Around line 23-30: The parsing in findTokenByChainSymbol using
chainSymbol.split('-') is brittle if chainName can contain dashes; change it to
locate the last dash (e.g., use lastIndexOf('-')) and split into chainName =
substring(0, lastDash) and symbol = substring(lastDash + 1), validate lastDash
>= 0 and both parts non-empty, and apply the same fix to the other occurrence
around the function/lines referenced (the second parser at lines ~68-71) so both
places safely handle chainNames containing dashes.

In @src/features/tokens/TokenList.tsx:
- Around line 10-16: TokenListProps uses the ChainName type but it isn't
imported, which will break TypeScript compilation; add an import for ChainName
at the top of TokenList.tsx alongside the existing type imports (e.g., where
Token and TokenSelectionMode are imported) so TokenListProps: interface
TokenListProps { ... chainFilter: ChainName | null; ... } resolves correctly.
- Around line 58-77: The search filter in TokenList's useMemo assumes token
fields like t.name and t.symbol exist and calls .toLowerCase() on them, which
can throw for missing values; update the filtered predicate to safely coerce
each possibly-missing field before calling toLowerCase() (e.g., use (t.name ||
'').toLowerCase(), (t.symbol || '').toLowerCase(), (t.addressOrDenom ||
'').toLowerCase(), (t.collateralAddressOrDenom || '').toLowerCase()), and ensure
chainDisplayName is also coerced (e.g., (getChainDisplayName(... ) ||
'').toLowerCase()) so the filter never invokes .toLowerCase() on undefined.

In @src/features/tokens/TokenListPanel.tsx:
- Around line 44-50: The TokenListPanel usage passes an unsupported aria-label
prop to SearchInput causing a TypeScript error; either remove the aria-label
from TokenListPanel's SearchInput invocation or update the SearchInput component
to accept an optional ariaLabel prop and forward it to the underlying <input>
(e.g., add ariaLabel?: string to SearchInput's props interface in
SearchInput.tsx and spread it as aria-label on the input element), then update
TokenListPanel to use ariaLabel if you choose the proper fix.

In @src/features/tokens/utils.ts:
- Around line 332-345: The current logic uses a fallback return of
routeTokens[0] which can return an unrelated token and the symbol-only match is
ambiguous; update the matcher to: (1) only match by collateral when
normalizedOriginCollateral and normalizedRouteCollateral exist; (2) only fall
back to symbol matching if the symbol is unambiguous (e.g., routeTokens.filter(t
=> t.symbol === originToken.symbol).length === 1); and (3) remove the final
fallback of "return matchingToken || routeTokens[0]" so the function returns
matchingToken (which may be undefined) and lets the caller handle no-match
cases. Ensure you touch the matchingToken find callback, the symbol-only branch,
and replace the final return to return matchingToken.

In @src/features/transfer/RecipientConfirmationModal.tsx:
- Around line 18-30: Guard the chainName before calling
getAccountAddressAndPubKey and trim the recipient value: first compute a trimmed
recipient (e.g., values.recipient?.trim()) and use that for truthiness checks,
and only call getAccountAddressAndPubKey when destinationToken and
destinationToken.chainName are present (otherwise set connectedDestAddress to
undefined/empty). Then set recipient = trimmedRecipient || connectedDestAddress
|| ''. Reference getAccountAddressAndPubKey, destinationToken, values.recipient,
connectedDestAddress and recipient when making the change.

In @src/features/transfer/TransferFeeModal.tsx:
- Around line 19-21: The Tailwind class "max-w-128" used on the Modal component
(Modal isOpen={isOpen} ... panelClassname="p-0 max-w-sm md:max-w-128
overflow-hidden") isn't defined in your tailwind config so it won't be
generated; open your tailwind.config.js and add a 128 entry under
theme.extend.maxWidth (e.g., map 128 to '32rem' or your desired value) so the
"md:max-w-128" utility exists and the modal width will behave as expected.

In @src/features/transfer/TransferTokenForm.tsx:
- Around line 148-155: The effect currently uses initialValues.originTokenKey so
it only runs for the initial selection; change it to read the live Formik value
instead (e.g. use useFormikContext() or receive values/originTokenKey prop) so
it reacts to user changes: inside the useEffect that calls getTokenByKey(tokens,
...), replace initialValues.originTokenKey with the current Formik
values.originTokenKey and keep setOriginChainName(originToken.chainName) as
before so the origin chain store updates whenever the user picks a different
origin token.
- Around line 378-412: The MaxButton currently passes values.recipient directly
to fetchMaxAmount which breaks when the UI relies on the connected destination
wallet default; compute the same "effective recipient" used by fees/validation
and pass that to fetchMaxAmount instead of values.recipient—i.e., derive the
fallback recipient from the connected accounts/selected destination token (same
logic used elsewhere in the form) and use that value in the fetchMaxAmount call
inside MaxButton before formatting and setFieldValue('amount', ...).

In @src/features/wallet/RecipientAddressModal.tsx:
- Around line 1-5: The file uses React.ChangeEvent types but doesn't import
React types; update the imports at the top of RecipientAddressModal (where
isValidAddress/ProtocolType/Modal/XIcon/useState/SolidButton are imported) to
include the type import (e.g., import type { ChangeEvent } from 'react' or
import React and use React.ChangeEvent) so the type references in the component
(e.g., the onChange/handleChange handlers around the RecipientAddressModal form,
including the code referenced in lines ~44-47) resolve and the module compiles.

In @src/styles/globals.css:
- Around line 5-27: The @font-face rules in globals.css reference
'/fonts/PPValve-PlainVariable.woff2' and '/fonts/PPFraktionMono-Variable.woff2'
but those files and the public/fonts directory are missing; fix by either adding
the actual .woff2 files to the public/fonts directory and committing them, or
update the src URLs in the 'PP Valve' and 'PP Fraktion Mono' @font-face
declarations to the correct existing asset paths (or a CDN) and verify the files
are served in production; ensure font filenames in the CSS exactly match the
committed asset names and that you keep font-display: swap.
🟡 Minor comments (10)
src/features/transfer/FeeSectionButton.tsx-26-32 (1)

26-32: Path fill colors don't match the text color states.

Look, the text goes from gray-500 to gray-600 on hover, which makes sense. But the [&_path]:fill-gray-600 stays the same in both states - it's gray-600 all the time. Meanwhile, you're passing Color.gray[500] to those icon components, but that CSS selector just tramples right over it like a donkey through a field.

If the icons are meant to follow the text color, the path fills should be gray-500 normally and gray-600 on hover:

🔧 Suggested fix
          <button
-            className="flex w-fit items-center font-secondary text-xxs text-gray-500 hover:text-gray-600 [&_path]:fill-gray-600 [&_path]:hover:fill-gray-600"
+            className="flex w-fit items-center font-secondary text-xxs text-gray-500 hover:text-gray-600 [&_path]:fill-gray-500 [&_path]:hover:fill-gray-600"
            type="button"
            onClick={open}
          >
src/components/buttons/SolidButton.tsx-33-41 (1)

33-41: Add explicit text color to gradient button variants

The gradients look layered nicely, but there's a gap: the accent-gradient and error-gradient variants are missing explicit text color. Looking at the Tailwind config, those gradient backgrounds are purely background-image definitions—no text styling baked in. Every other variant sets text-white or similar, but accent and red don't. That means text color just inherits from the browser defaults, which could get murky against those gradient backgrounds.

Add text-white (or whatever works with yer design) to lines 34 and 40 to match the pattern of other variants. Like onions, buttons have layers—backgrounds, text, shadows—and they're all gotta work together.

src/components/input/SearchInput.tsx-29-29 (1)

29-29: Remove the redundant all: responsive breakpoint prefix from base styles.

The all breakpoint is configured in the Tailwind config as screens: { all: '1px' }, meaning it applies at 1px and above—basically always. Using all:border-gray-300 is like putting the same thing twice in your porridge; it works, but there's no point. Just use border-gray-300 py-2 text-sm focus:border-blue-400 without the all: prefix, since these are base styles that should apply everywhere anyway.

src/features/transfer/maxAmount.ts-50-55 (1)

50-55: Consider adding error feedback when fetchMaxAmount returns undefined.

Callers do handle the undefined return via the isNullish check, but it silently returns without user-facing feedback. When either the route lookup or destination token fails, the click handler exits without informing the user why the max amount couldn't be calculated—they'll just see the button appear unchanged. Adding a toast notification or error message would make this clearer rather than leaving it as a silent no-op.

src/features/tokens/TokenSelectField.tsx-159-163 (1)

159-163: Fix the CSS class typo - missing hyphen in cursor-pointer.

There's a wee typo in your styles object that'll keep the cursor from changing on hover.

🔧 Proposed fix
 const styles = {
   base: 'w-full py-2 flex items-center justify-between transition-all rounded-xl px-1.5',
-  enabled: 'hover:bg-gray-50 cursor pointer',
+  enabled: 'hover:bg-gray-50 cursor-pointer',
   disabled: 'cursor-not-allowed opacity-60',
 };
.gitignore-40-40 (1)

40-40: Consider documenting where to obtain the custom fonts.

The fonts are actually optional—the CSS already includes system font fallbacks (['PP Fraktion Mono', 'system-ui', 'sans-serif'] and ['PP Valve', 'system-ui', 'sans-serif']), and font-display: swap handles graceful degradation. So ignoring /public/fonts won't break anything for new developers.

That said, there's a documentation gap. The CSS comment tells folks where to put the fonts (public/fonts/), but doesn't explain where to get them in the first place. If you want developers to use these custom fonts, add a note to the README or CUSTOMIZE.md about how to obtain PP Fraktion Mono and PP Valve (whether that's purchasing, downloading, or generating them).

src/features/chains/ChainFilterPanel.tsx-36-41 (1)

36-41: Remove unsupported aria-label prop from SearchInput.

The SearchInput component only accepts inputRef, value, onChange, and placeholder props. The aria-label attribute won't be passed through to the underlying input element. Either add support for aria-label in SearchInput or remove it from the usage here.

src/components/nav/Nav.tsx-48-60 (1)

48-60: Add rel="noopener noreferrer" for external links, will ye?

When ye open links in new tabs with target="_blank", the new page can access the opener window through window.opener. Adding rel="noopener noreferrer" prevents this - it's a wee security thing that keeps everything in its proper place.

🔒 Proposed fix
    <Link
      ref={ref}
      className={clsx(
        'flex items-center gap-2 text-primary-500 decoration-primary-500 underline-offset-2 hover:underline',
        className,
      )}
      target="_blank"
+     rel="noopener noreferrer"
      href={item.url}
    >
src/features/transfer/TransferTokenForm.tsx-894-899 (1)

894-899: Validation error field key looks wrong.
Invalid recipient is attached to amount.

Proposed fix
-    if (!recipient) return [{ amount: 'Invalid recipient' }, null];
+    if (!recipient) return [{ recipient: 'Invalid recipient' }, null];
src/features/tokens/hooks.ts-94-101 (1)

94-101: Destination fallback match is case-sensitive.
This can miss the token if symbol casing differs.

Proposed fix
-    destinationToken = connectedChain
-      ? tokens.find((t) => t.chainName === connectedChain && t.symbol === connectedSymbol)
+    destinationToken = connectedChain
+      ? tokens.find(
+          (t) =>
+            t.chainName === connectedChain &&
+            t.symbol.toLowerCase() === (connectedSymbol ?? '').toLowerCase(),
+        )
       : undefined;
🧹 Nitpick comments (23)
src/pages/_app.tsx (1)

36-39: The comment might be outdated now that you've switched to Tailwind font classes.

The comment mentions needing font definitions "both here and in _document.tsx" for Next.js to load fonts, but since you've moved from MAIN_FONT.variable to the static Tailwind class font-primary, this comment might not be accurate anymore. Either update the comment to reflect the new approach or remove it if it no longer applies.

✏️ Consider updating the comment
-  // Note, the font definition is required both here and in _document.tsx
-  // Otherwise Next.js will not load the font
+  // Apply primary font and base text color to the app root
   return (
     <div className="font-primary text-black">
src/components/icons/HyperlaneTransparentLogo.tsx (1)

3-18: Consider accepting props for flexibility.

Now look, this logo's got no way to change its size or color from the outside - it's stuck with hardcoded dimensions and a white stroke. Unlike the other icons in your codebase that accept DefaultIconProps, this one's a bit set in its ways.

If you ever need to resize it or use it somewhere with a light background, you'll be in a pickle. Might want to make it a bit more flexible, unless you're certain it'll only ever be used one way.

♻️ Suggested enhancement for prop support
 import { memo } from 'react';
+import { DefaultIconProps } from '@hyperlane-xyz/widgets';

-function _HyperlaneTransparentLogo() {
+function _HyperlaneTransparentLogo({ color = 'white', ...props }: DefaultIconProps) {
   return (
     <svg
       width="146"
       height="127"
       viewBox="0 0 146 127"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
+      {...props}
     >
       <path
         d="M111.997 0.5C118.021..."
-        stroke="white"
+        stroke={color}
       />
     </svg>
   );
 }
src/styles/globals.css (1)

78-96: Consider using theme tokens for scrollbar colors.

The hardcoded hex values #e8caff and #d4a3ff work fine, but if you ever want to support theming or dark mode, pulling these from CSS variables or the theme config would make life easier down the road. Not a dealbreaker by any means - scrollbar styling can be finicky with variables in some browsers.

src/features/transfer/RecipientConfirmationModal.tsx (1)

41-41: Make the recipient address easier to visually verify (wrap/monospace).

Addresses can be long; consider break-all + font-mono so folks don’t miss a sneaky character.

Proposed tweak
-      <p className="rounded-lg bg-primary-500/5 p-2 text-center text-sm">{recipient}</p>
+      <p className="break-all rounded-lg bg-primary-500/5 p-2 text-center font-mono text-sm">
+        {recipient}
+      </p>
src/features/tokens/TokenListPanel.tsx (2)

10-21: Consider collapsing chainFilter + selectedChain into a single source of truth.

Two “selected chain” props is like onion layers you don’t need—easy for state to drift (desktop sets chainFilter, mobile sets selectedChain).


8-9: Type preferredChains as ChainName[] to match MobileChainQuickSelect.

Right now it's typed as string[], but the component expects ChainName[] (from @hyperlane-xyz/sdk). Typing it properly upfront keeps things from gettin' messy with casts down the road.

src/features/wallet/RecipientAddressModal.tsx (1)

62-73: Add minimal input a11y wiring (label/aria-invalid/aria-describedby).

Small tweak, big payoff for screen readers and form tooling.

Proposed tweak
       <div className="px-4 pb-4">
+        <label htmlFor="recipient-address" className="sr-only">
+          Receive address
+        </label>
         <input
+          id="recipient-address"
           type="text"
           value={address}
           onChange={handleAddressChange}
           placeholder="Paste Wallet Address"
+          aria-invalid={!!error}
+          aria-describedby={error ? 'recipient-address-error' : undefined}
           className={`w-full rounded-lg border px-4 py-3 text-sm text-gray-700 placeholder:text-gray-400 focus:outline-none ${
             error
               ? 'border-red-500 focus:border-red-500'
               : 'border-gray-300 focus:border-primary-500'
           }`}
         />
-        {error && <p className="mt-1 text-sm text-red-500">{error}</p>}
+        {error && (
+          <p id="recipient-address-error" className="mt-1 text-sm text-red-500">
+            {error}
+          </p>
+        )}
src/components/nav/Header.tsx (1)

17-17: Consider adding meaningful alt text for accessibility.

The images use empty alt attributes (alt=""). While this is fine for purely decorative images, the logo and name images likely convey meaning to screen reader users. Consider adding descriptive alt text like alt="App logo" and alt="App name".

♿ Suggested accessibility improvement
-          <Image src={Logo} width={36} alt="" className="h-auto" />
+          <Image src={Logo} width={36} alt="App logo" className="h-auto" />
-          <Image src={Logo} width={46} alt="" className="h-auto" />
-          <Image src={Name} width={150} alt="" className="ml-1.5" />
+          <Image src={Logo} width={46} alt="App logo" className="h-auto" />
+          <Image src={Name} width={150} alt="App name" className="ml-1.5" />

Also applies to: 36-39

src/components/icons/HyperlaneGradientLogo.tsx (1)

4-28: Nice and tidy icon component.

The memoization is appropriate here since this is a static SVG. One small thing to consider: if this is purely decorative, adding aria-hidden="true" would let screen readers know to skip over it. Not a big deal, just a wee bit of polish.

♿ Optional accessibility enhancement
-    <svg viewBox="0 0 219 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
+    <svg viewBox="0 0 219 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" {...props}>
src/features/chains/ChainList.tsx (1)

86-104: Style conflict in ChainButton.

The styles.label class applies text-sm font-normal, but Line 97 also applies text-sm font-medium. These conflicting font weights might cause unexpected behavior depending on CSS specificity. Consider consolidating.

♻️ Proposed fix - remove duplicate text-sm and clarify font weight intent
 const styles = {
-  label: 'font-secondary text-sm font-normal',
+  label: 'font-secondary',
 };

Or remove the redundant classes from the span:

-      <span className="truncate text-sm font-medium">{label}</span>
+      <span className="truncate">{label}</span>
src/features/chains/MobileChainQuickSelect.tsx (1)

44-52: Consider using a Map for chain lookups, eh?

Right now ye're doing an O(n) search through allChains for every preferred chain. With many chains, this could get a wee bit sluggish - like trudging through the swamp. A simple Map would make this cleaner.

🔧 Minor optimization
      // Get preferred chains that exist, maintaining preferred order
+     const chainMap = new Map(allChains.map((c) => [c.name, c]));
      const preferred = preferredChains
        .filter((name) => chainNameSet.has(name))
        .map((name) => {
-         const chain = allChains.find((c) => c.name === name);
+         const chain = chainMap.get(name);
          return {
            name,
            displayName: chain?.displayName || name,
          };
        });
src/features/wallet/WalletDropdown.tsx (2)

50-56: Consider showing a toast on disconnect failure.

Right now the error just gets logged, but the user won't know their wallet didn't actually disconnect. They might wander off thinking they're safe when they're not - like leaving me swamp gate open.

🛠️ Proposed improvement
  const onDisconnect = useCallback(async () => {
    try {
      await disconnectFn?.();
    } catch (err) {
      logger.error('Failed to disconnect wallet', err);
+     toast.error('Failed to disconnect wallet. Please try again.');
    }
  }, [disconnectFn]);

You'd need to import toast from react-toastify.


203-215: That green dot might be a bit too subtle.

bg-green-50 is quite light - might blend into the background on some displays. Consider using bg-green-500 or similar for better visibility when showing the connected state.

🎨 Color suggestion
      {address ? (
-       <div className="h-2 w-2 rounded-full bg-green-50" />
+       <div className="h-2 w-2 rounded-full bg-green-500" />
      ) : (
src/features/tokens/TokenList.tsx (2)

35-56: startTransition doesn’t make the route computation itself non-blocking.
The heavy loop still runs on the main thread; only the state update is deprioritized. If allTokens is big, this can still hitch the UI.


125-133: Consider making “no route” tokens non-clickable (or clearly explained).
Right now they’re greyed out but still selectable; users will walk into a “Route is not supported” later.

One possible tweak
-              <TokenButton key={tokenKey} token={token} onSelect={onSelect} hasRoute={hasRoute} />
+              <TokenButton
+                key={tokenKey}
+                token={token}
+                onSelect={onSelect}
+                hasRoute={hasRoute}
+              />
 function TokenButton({
   token,
   onSelect,
   hasRoute,
 }:{
   ...
 }) {
   ...
   return (
     <button
       type="button"
+      disabled={!hasRoute}
       className={`group mb-1.5 flex w-full items-center rounded-[3px] px-3 py-2.5 transition-colors hover:bg-gray-100 ${
         !hasRoute ? 'opacity-40' : ''
       }`}
       onClick={() => onSelect(token)}
     >

Also applies to: 171-178

src/features/analytics/utils.ts (1)

70-74: Consider still tracking failures even when destinationTokenKey can’t be resolved.
Right now you return if destToken isn’t found, which can hide real breakages in token resolution.

src/features/transfer/TransfersDetailsModal.tsx (1)

171-174: Kill the commented-out UI block once you’re sure.
Keeping dead UI in-line tends to linger like swamp fog.

src/features/transfer/TransferTokenForm.tsx (1)

224-232: URL params use chain+symbol (can be ambiguous).
If two tokens share a symbol on a chain (or you add “import token”), restoring state from URL may pick the wrong one. Token keys (or address/denom) would be sturdier.

Also applies to: 836-846

src/features/transfer/useFeeQuotes.ts (1)

107-117: Avoid as Token casts for destinationToken in stableswap detection.
Either widen isStableSwapRoute to accept IToken or narrow with a real type guard—casts can hide real mismatches.

tailwind.config.js (1)

124-130: Optional: error-glow uses the same color as accent-glow.
If that’s intentional, all good; if not, you may want it keyed off red to match the name.

src/features/tokens/utils.ts (3)

62-69: The as any cast on Line 67 is a bit like covering up a hole in me swamp with a rug.

The comment explains why this workaround exists (some standards like EvmHypCollateralFiat may not be in TOKEN_COLLATERALIZED_STANDARDS), which is good. However, this as any cast bypasses type checking entirely. If the SDK type for standard ever changes, this won't catch it.

Consider filing an issue upstream to include missing standards in TOKEN_COLLATERALIZED_STANDARDS, or create a local extended list that's properly typed.


219-239: Minor inconsistency in key format, like having different signs for the same swamp entrance.

Line 234 includes protocol in the HypNative key (chainName-symbol-hypnative-protocol), but lines 231 and 238 don't include protocol. While collisions across protocols are rare (same chain name with same collateral address but different protocols), including protocol consistently would future-proof the key format.

This is a minor nit; the current implementation should work fine for typical scenarios.


261-265: These as any casts smell worse than onion layers left out in the sun.

Lines 261-262 cast to any to access stableSwapPool. This bypasses type safety entirely. If the SDK's Token type doesn't include stableSwapPool, consider:

  1. Extending the type locally with an interface that includes this property
  2. Using a type guard to safely access the property
♻️ Suggested type-safe approach
interface StableSwapToken extends Token {
  stableSwapPool?: string;
}

function hasStableSwapPool(token: Token): token is StableSwapToken {
  return 'stableSwapPool' in token;
}

// Then in isStableSwapRoute:
if (!hasStableSwapPool(originToken) || !hasStableSwapPool(destToken)) {
  return false;
}
const originPool = originToken.stableSwapPool;
const destPool = destToken.stableSwapPool;

Comment thread package.json
Comment on lines +24 to 26
"@hyperlane-xyz/sdk": "20.2.0-stableswap.2a0d563060e1037fa228687c3c9a3ecea60dce9f",
"@hyperlane-xyz/utils": "20.2.0-stableswap.2a0d563060e1037fa228687c3c9a3ecea60dce9f",
"@hyperlane-xyz/widgets": "20.1.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Oi, these pre-release versions shouldn't wander into the main branch.

You've got @hyperlane-xyz/sdk and @hyperlane-xyz/utils pinned to 20.2.0-stableswap pre-release versions with a commit hash. That's fine for your swamp demo, but these should be reverted to published versions before this gets merged.

Also worth noting — @hyperlane-xyz/widgets is still at 20.1.0 while the others are at 20.2.0-stableswap. Might want to keep these in sync to avoid any unexpected surprises down the road.

Based on learnings, developers may temporarily use these paths for testing but should revert before merging.

🤖 Prompt for AI Agents
In @package.json around lines 24 - 26, The package.json currently pins
@hyperlane-xyz/sdk and @hyperlane-xyz/utils to pre-release stableswap builds
with commit hashes and leaves @hyperlane-xyz/widgets at 20.1.0; revert those two
entries to the published stable versions (remove the "-stableswap.*" pins and
commit hashes) and align all three @hyperlane-xyz/* packages to the same
published version (e.g., set @hyperlane-xyz/sdk, @hyperlane-xyz/utils, and
@hyperlane-xyz/widgets to the current stable release) so no pre-release or
commit-hash pins land on main.

Comment on lines 32 to +33
const defaultClassName =
'mt-1.5 px-2.5 py-2.5 text-sm rounded-lg border border-primary-300 focus:border-primary-500 disabled:bg-gray-150 outline-none transition-all duration-300';
'rounded-lg border border-primary-300 focus:border-primary-500 disabled:bg-gray-150 outline-none transition-all duration-300';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all usages of TextField and TextInput components
echo "=== TextField usages ==="
rg -n '<TextField' --type tsx --type ts -B 2 -A 2

echo ""
echo "=== TextInput usages ==="
rg -n '<TextInput' --type tsx --type ts -B 2 -A 2

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 207


🏁 Script executed:

#!/bin/bash
# Find all usages of TextField and TextInput components
echo "=== TextField usages ==="
rg '<TextField' -g '*.tsx' -g '*.ts' -B 2 -A 2

echo ""
echo "=== TextInput usages ==="
rg '<TextInput' -g '*.tsx' -g '*.ts' -B 2 -A 2

echo ""
echo "=== Check for TextField/TextInput in the component file itself ==="
cat -n src/components/input/TextField.tsx

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 2498


🏁 Script executed:

#!/bin/bash
# Get full definitions of components using TextField/TextInput
echo "=== TransferTokenForm.tsx - TextField usage ==="
sed -n '1,150p' src/features/transfer/TransferTokenForm.tsx | grep -A 10 '<TextField'

echo ""
echo "=== SelectOrInputTokenIds.tsx - TextField usage ==="
sed -n '1,100p' src/features/tokens/SelectOrInputTokenIds.tsx | grep -A 10 '<TextField'

echo ""
echo "=== SearchInput.tsx - TextInput usage ==="
cat -n src/components/input/SearchInput.tsx

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1477


🏁 Script executed:

#!/bin/bash
# Get full TransferTokenForm to see TextField usage
echo "=== TransferTokenForm.tsx - Full file to find TextField usage ==="
rg -B 5 -A 10 'TextField' src/features/transfer/TransferTokenForm.tsx

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1829


Add padding and text-size classes to TextField usages—some components are left exposed.

So here's the thing: while you've stripped out the default padding and text size, not all the places using these components have gotten the memo. Take SelectOrInputTokenIds.tsx—it's only got className="w-full", which means that input's gonna feel pretty tight with no breathing room at all. Over in TransferTokenForm.tsx, there's text-xl for size, but still no padding, so it's like you've got text but nowhere comfortable for it to sit. Even SearchInput.tsx is throwing this odd all:py-2 syntax around that doesn't follow standard Tailwind conventions.

These inputs are gonna look like they're all squeezed together in a swamp with nowhere to move. Either bring back some sensible defaults or make sure every usage explicitly handles its own padding (px-* and py-*) and text sizing.

🤖 Prompt for AI Agents
In @src/components/input/TextField.tsx around lines 32 - 33, The default
TextField styling removed padding and text-size which leaves callers like
SelectOrInputTokenIds (className="w-full"), TransferTokenForm (uses text-xl but
no padding), and SearchInput (uses invalid "all:py-2") rendering cramped;
restore sensible defaults by updating the defaultClassName constant in
TextField.tsx to include padding (e.g., px-3 py-2) and a base text-size (e.g.,
text-sm or text-base), and then fix usages: either remove redundant
sizing/padding from callers or add explicit padding/text classes where needed
(update TransferTokenForm to add px/py if it wants text-xl), and replace the
invalid "all:py-2" in SearchInput with a valid Tailwind class like "py-2".

Comment thread src/consts/args.ts
Comment on lines 3 to 8
export enum WARP_QUERY_PARAMS {
ORIGIN = 'origin',
DESTINATION = 'destination',
TOKEN = 'token',
ORIGIN_TOKEN = 'originToken',
DESTINATION_TOKEN = 'destinationToken',
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for references to the old TOKEN param and new originToken/destinationToken
echo "=== Searching for old WARP_QUERY_PARAMS.TOKEN references ==="
rg -n "WARP_QUERY_PARAMS\.TOKEN\b" src/ 2>/dev/null || echo "No matches found"

echo -e "\n=== Searching for string 'token' as query param ==="
rg -n "['\"]token['\"]" src/ | grep -v node_modules | head -20

echo -e "\n=== Searching for originToken/destinationToken usage ==="
rg -n "originToken|destinationToken" src/ | head -30

echo -e "\n=== Checking URL parsing/routing layer ==="
fd -e ts -e tsx "route|parse|query|param" src/ | head -15

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# Find the actual args.ts file and check nearby files
echo "=== Looking at args.ts and related query param handling ==="
find src -name "*args*" -o -name "*query*" -o -name "*param*" | head -20

# Check if there's any legacy/migration code
echo -e "\n=== Searching for legacy/migration/compat handling ==="
rg -n "legacy|migration|compat|backward|fallback" src/ -i | grep -v node_modules | head -20

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 2423


🏁 Script executed:

#!/bin/bash
echo "=== Examining queryParams.ts ==="
cat -n src/utils/queryParams.ts

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1095


Add backward compatibility handling for old ?token= query parameters.

Old bookmarks and deep links using ?token=... will silently break with this change. The queryParams utility has no migration logic to map the old single token param to the new originToken and destinationToken split. Either add a URL parsing layer that translates legacy links, or update any public documentation/tooling that generates these links.

🤖 Prompt for AI Agents
In @src/consts/args.ts around lines 3 - 8, The change removed the legacy single
"token" query parameter and breaks old deep links; update the queryParams
parsing logic to detect a legacy "token" param and map it into the new keys
(WARP_QUERY_PARAMS.ORIGIN_TOKEN and WARP_QUERY_PARAMS.DESTINATION_TOKEN) when
those new params are not present; modify the queryParams utility to prefer
explicit originToken/destinationToken but fall back to populating both from
"token" for backward compatibility, and update related tests to cover legacy and
new-param behaviors.

Comment thread src/consts/warpRouteWhitelist.ts Outdated
Comment on lines +5 to +9
interface ChainFilterPanelProps {
searchQuery: string;
onSearchChange: (s: string) => void;
selectedChain: ChainName | null;
onSelectChain: (chain: ChainName | null) => void;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing ChainName type import again.

Ye need to bring ChainName into the file, otherwise TypeScript will be grumpier than an ogre with uninvited visitors.

🐛 Proposed fix
 import { ChevronIcon } from '@hyperlane-xyz/widgets';
+import { ChainName } from '@hyperlane-xyz/sdk';
 import { SearchInput } from '../../components/input/SearchInput';
 import { ChainList } from './ChainList';
🤖 Prompt for AI Agents
In @src/features/chains/ChainFilterPanel.tsx around lines 5 - 9, The interface
ChainFilterPanelProps references the type ChainName but the file is missing its
import; add an import for the ChainName type (the same exported symbol used
elsewhere in the project) at the top of the file so TypeScript recognizes
ChainName and the props signature for ChainFilterPanelProps compiles correctly.

Comment on lines +19 to +21
<Modal isOpen={isOpen} close={close} panelClassname="p-0 max-w-sm md:max-w-128 overflow-hidden">
<ModalHeader>Fee Details</ModalHeader>
<div className="flex w-full flex-col items-start gap-2 p-4 text-sm">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and check the TransferFeeModal.tsx file
fd -t f "TransferFeeModal.tsx" -x cat -n {}

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 3857


🏁 Script executed:

# Search for Tailwind config files
fd -t f "tailwind\\.config\\.(js|ts|cjs|mjs)" -o "tailwind.config.ts" -o "tailwind.config.js"

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 239


🏁 Script executed:

# Search for max-w-128 definition across the codebase
rg "max-w-128" -A 2 -B 2

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 535


🏁 Script executed:

# Search for Tailwind config at root
fd "tailwind.config" -t f

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 102


🏁 Script executed:

# Also check for any custom CSS files that might define max-w-128
rg "max-w-128|128" --type css -B 2 -A 2

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 66


🏁 Script executed:

# Check tsconfig and package.json to understand project structure better
cat -n tailwind.config.ts tailwind.config.js 2>/dev/null | head -50

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1695


🏁 Script executed:

# Read the complete tailwind config file
cat tailwind.config.js

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 4238


Add max-w-128 to your Tailwind config's maxWidth extend.

The max-w-128 class you're using on line 19 isn't defined in your tailwind.config.js—only the spacing utility has 128. The class won't be generated, so your responsive max-width won't work as intended. Pop this into the maxWidth section under extend:

maxWidth: {
  'xl-1': '39.5rem',
  128: '32rem',  // Add this
},

This way the modal will respect the width constraint on medium screens and up.

🤖 Prompt for AI Agents
In @src/features/transfer/TransferFeeModal.tsx around lines 19 - 21, The
Tailwind class "max-w-128" used on the Modal component (Modal isOpen={isOpen}
... panelClassname="p-0 max-w-sm md:max-w-128 overflow-hidden") isn't defined in
your tailwind config so it won't be generated; open your tailwind.config.js and
add a 128 entry under theme.extend.maxWidth (e.g., map 128 to '32rem' or your
desired value) so the "md:max-w-128" utility exists and the modal width will
behave as expected.

Comment on lines +148 to 155
// Update origin chain name in store when origin token changes
useEffect(() => {
if (!originChainName) setOriginChainName(initialValues.origin);
}, [initialValues.origin, originChainName, setOriginChainName]);
const originToken = getTokenByKey(tokens, initialValues.originTokenKey);
if (originToken) {
setOriginChainName(originToken.chainName);
}
}, [initialValues.originTokenKey, tokens, setOriginChainName]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: origin chain store updates only for initial token, not user changes.
This effect listens to initialValues.originTokenKey, which won’t change when the user picks a new origin token in Formik.

Fix idea: drive it off Formik `values.originTokenKey`
-  // Update origin chain name in store when origin token changes
-  useEffect(() => {
-    const originToken = getTokenByKey(tokens, initialValues.originTokenKey);
-    if (originToken) {
-      setOriginChainName(originToken.chainName);
-    }
-  }, [initialValues.originTokenKey, tokens, setOriginChainName]);
+  // Update origin chain name in store when origin token changes
+  // (must follow Formik state, not the initial snapshot)
+  // Move into a child component that can use `useFormikContext`,
+  // or add a small component here under <Formik>.
🤖 Prompt for AI Agents
In @src/features/transfer/TransferTokenForm.tsx around lines 148 - 155, The
effect currently uses initialValues.originTokenKey so it only runs for the
initial selection; change it to read the live Formik value instead (e.g. use
useFormikContext() or receive values/originTokenKey prop) so it reacts to user
changes: inside the useEffect that calls getTokenByKey(tokens, ...), replace
initialValues.originTokenKey with the current Formik values.originTokenKey and
keep setOriginChainName(originToken.chainName) as before so the origin chain
store updates whenever the user picks a different origin token.

Comment on lines +378 to +412
function MaxButton({
balance,
disabled,
isRouteSupported,
}: {
balance?: TokenAmount;
disabled?: boolean;
isRouteSupported: boolean;
}) {
const { values, setFieldValue } = useFormikContext<TransferFormValues>();
const { originTokenKey, destinationTokenKey } = values;
const tokens = useTokens();
const originToken = getTokenByKey(tokens, originTokenKey);
const destinationToken = getTokenByKey(tokens, destinationTokenKey);
const multiProvider = useMultiProvider();
const { accounts } = useAccounts(multiProvider);
const { fetchMaxAmount, isLoading } = useFetchMaxAmount();

const isDisabled =
disabled || !isRouteSupported || isLoading || !balance || !originToken || !destinationToken;

const onClick = async () => {
if (isDisabled) return;
const maxAmount = await fetchMaxAmount({
balance,
origin: originToken.chainName,
destination: destinationToken.chainName,
accounts,
recipient: values.recipient,
});
if (isNullish(maxAmount)) return;
const decimalsAmount = maxAmount.getDecimalFormattedAmount();
const roundedAmount = new BigNumber(decimalsAmount).toFixed(4, BigNumber.ROUND_FLOOR);
setFieldValue('amount', roundedAmount);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Max button should use the same “effective recipient” fallback as fees/validation.
Right now it passes values.recipient straight through; if the user relies on the connected destination wallet default, Max may never work.

Minimal fix (compute effective recipient like elsewhere)
   const onClick = async () => {
     if (isDisabled) return;
+    const { address: connectedDestAddress } = getAccountAddressAndPubKey(
+      multiProvider,
+      destinationToken.chainName,
+      accounts,
+    );
+    const recipient = values.recipient || connectedDestAddress || '';
+    if (!recipient) return;
     const maxAmount = await fetchMaxAmount({
       balance,
       origin: originToken.chainName,
       destination: destinationToken.chainName,
       accounts,
-      recipient: values.recipient,
+      recipient,
     });
🤖 Prompt for AI Agents
In @src/features/transfer/TransferTokenForm.tsx around lines 378 - 412, The
MaxButton currently passes values.recipient directly to fetchMaxAmount which
breaks when the UI relies on the connected destination wallet default; compute
the same "effective recipient" used by fees/validation and pass that to
fetchMaxAmount instead of values.recipient—i.e., derive the fallback recipient
from the connected accounts/selected destination token (same logic used
elsewhere in the form) and use that value in the fetchMaxAmount call inside
MaxButton before formatting and setFieldValue('amount', ...).

Comment on lines +1 to +5
import { isValidAddress, ProtocolType } from '@hyperlane-xyz/utils';
import { Modal, XIcon } from '@hyperlane-xyz/widgets';
import { useState } from 'react';
import { SolidButton } from '../../components/buttons/SolidButton';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

This will fail to compile: React.ChangeEvent used without importing React types.

Bring in ChangeEvent (or type React) from react so the type reference exists.

Proposed fix
 import { isValidAddress, ProtocolType } from '@hyperlane-xyz/utils';
 import { Modal, XIcon } from '@hyperlane-xyz/widgets';
-import { useState } from 'react';
+import { useState, type ChangeEvent } from 'react';
 import { SolidButton } from '../../components/buttons/SolidButton';
@@
-  const handleAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  const handleAddressChange = (e: ChangeEvent<HTMLInputElement>) => {
     setAddress(e.target.value);
     if (error) setError('');
   };

Also applies to: 44-47

🤖 Prompt for AI Agents
In @src/features/wallet/RecipientAddressModal.tsx around lines 1 - 5, The file
uses React.ChangeEvent types but doesn't import React types; update the imports
at the top of RecipientAddressModal (where
isValidAddress/ProtocolType/Modal/XIcon/useState/SolidButton are imported) to
include the type import (e.g., import type { ChangeEvent } from 'react' or
import React and use React.ChangeEvent) so the type references in the component
(e.g., the onChange/handleChange handlers around the RecipientAddressModal form,
including the code referenced in lines ~44-47) resolve and the module compiles.

Comment thread src/styles/globals.css
Comment on lines +5 to +27
/*
Custom Fonts (optional - falls back to system fonts if not available)
Place font files in public/fonts/ directory
=====================================================================
*/

/* PP Valve - Secondary font */
@font-face {
font-family: 'PP Valve';
src: url('/fonts/PPValve-PlainVariable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}

/* PP Fraktion Mono - Primary font */
@font-face {
font-family: 'PP Fraktion Mono';
src: url('/fonts/PPFraktionMono-Variable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check public/fonts directory structure and font files
find public/fonts -type f 2>/dev/null | head -20 || echo "No public/fonts directory found"

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Also check if public directory exists and its structure
ls -la public/ 2>/dev/null | head -20 || echo "No public directory found"

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1079


🏁 Script executed:

#!/bin/bash
# Search for .woff2 font files anywhere in repo
find . -name "*.woff2" 2>/dev/null

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Also check if there's a fonts directory anywhere
find . -type d -name "fonts" 2>/dev/null

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 66


Those font files you're referencing? Yeah, they're not actually in the repository.

Look, I'm not tryin' to be difficult here, but the CSS is pointin' to /fonts/PPValve-PlainVariable.woff2 and /fonts/PPFraktionMono-Variable.woff2, and they don't exist nowhere in the codebase. There's no public/fonts/ directory at all.

The @font-face declarations are properly written with the variable font syntax and font-display: swap is a nice touch for performance, but without the actual .woff2 files, they're not gonna load. Sure, the CSS comment mentions fallin' back to system fonts, which technically happens, but that means users won't get your custom fonts.

You need to either:

  1. Add those font files to public/fonts/ directory, or
  2. Point the CSS to wherever you're actually keepin' 'em

Get those fonts in place and this'll be set.

🤖 Prompt for AI Agents
In @src/styles/globals.css around lines 5 - 27, The @font-face rules in
globals.css reference '/fonts/PPValve-PlainVariable.woff2' and
'/fonts/PPFraktionMono-Variable.woff2' but those files and the public/fonts
directory are missing; fix by either adding the actual .woff2 files to the
public/fonts directory and committing them, or update the src URLs in the 'PP
Valve' and 'PP Fraktion Mono' @font-face declarations to the correct existing
asset paths (or a CDN) and verify the files are served in production; ensure
font filenames in the CSS exactly match the committed asset names and that you
keep font-display: swap.

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: 1

🤖 Fix all issues with AI agents
In `@src/consts/warpRoutes.yaml`:
- Around line 122-153: The Linea and Mantra entries (chainName: linea and
chainName: mantra) currently reuse the same token address in the fields
addressOrDenom and adapterAddress (0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895);
update these fields so each chain uses its correct deployed token and adapter
addresses (or remove adapterAddress if not applicable) instead of the copied
value — change addressOrDenom and adapterAddress in the linea block and/or
mantra block to the proper per-chain addresses to match their
helperAddress/collateralAddressOrDenom differences.

Comment thread src/consts/warpRoutes.yaml Outdated
Comment on lines +122 to +153
- chainName: linea
standard: EvmHypStableSwap
decimals: 6
symbol: mUSD
name: MetaMask USD
addressOrDenom: '0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895'
collateralAddressOrDenom: '0xaca92e438df0b2401ff60da7e4337b687a2435da'
stableSwapPool: mainnet-stableswap-pool
helperAddress: '0x3856c8A14cf1D1da69237B8e79aDb4E3eE44fba2'
sUSDAddress: '0x38E8720EBE02e7c5254F9De9F81440C7a770a9c6'
adapterAddress: '0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895'
connections:
- token: ethereum|arbitrum|0x94C62e7958738B65737a0Db8A5077def3AED84AA
- token: ethereum|arbitrum|0x6BEC839292A36372882Cb850E93FB5aC2A9BA4Af
- token: ethereum|base|0x73Ef899fDa87213e26501707ab585028BFB297c8
- token: ethereum|base|0x23c51024b19303F1315DbFFA055666aE9B7A0B2c
- token: ethereum|soneium|0x867D428B8FbE196EA4e997e7980623E75ED219a7
- token: ethereum|hyperevm|0xc7d07C8acFC66852BAad9Afb49E8A3AA7F6D3575
- token: ethereum|mantra|0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895

# MANTRA mantraUSD
- chainName: mantra
standard: EvmHypStableSwap
decimals: 6
symbol: mantraUSD
name: MANTRA USD
addressOrDenom: '0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895'
collateralAddressOrDenom: '0xd2b95283011E47257917770D28Bb3EE44c849f6F'
stableSwapPool: mainnet-stableswap-pool
helperAddress: '0xF0d9D232141dF68DD3CE272dd216C8D71645F25A'
sUSDAddress: '0x38E8720EBE02e7c5254F9De9F81440C7a770a9c6'
adapterAddress: '0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for the shared address across all file types
rg -n "0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895" --type-list | head -5
rg -n "0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895"

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1499


🏁 Script executed:

#!/bin/bash
# Also search for the Linea-specific chain config and Mantra-specific chain config
# to understand if this is a legitimate cross-chain address
rg -B5 -A10 "chainName: linea" src/consts/warpRoutes.yaml
rg -B5 -A10 "chainName: mantra" src/consts/warpRoutes.yaml

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 1538


🏁 Script executed:

#!/bin/bash
# Check git history to see if there's a copy/paste pattern or comments
git log --oneline -n 20 -- src/consts/warpRoutes.yaml 2>/dev/null || echo "Git history not available"

Repository: hyperlane-xyz/hyperlane-warp-ui-template

Length of output: 106


Fix the duplicated token address across Linea and Mantra. Both addressOrDenom and adapterAddress use 0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895 — the same address for different chains. That's a copy/paste slip waiting to happen. Each chain needs its own token deployment, not the same address. The fact that helperAddress and collateralAddressOrDenom are different between the two entries shows they were meant to be customized, so the primary address should be too.

🤖 Prompt for AI Agents
In `@src/consts/warpRoutes.yaml` around lines 122 - 153, The Linea and Mantra
entries (chainName: linea and chainName: mantra) currently reuse the same token
address in the fields addressOrDenom and adapterAddress
(0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895); update these fields so each chain
uses its correct deployed token and adapter addresses (or remove adapterAddress
if not applicable) instead of the copied value — change addressOrDenom and
adapterAddress in the linea block and/or mantra block to the proper per-chain
addresses to match their helperAddress/collateralAddressOrDenom differences.

- Update all helper addresses to new deployments (Feb 2026)
- Add missing connections for arbitrum/base USDC/USDT to hyperevm, linea, mantra
- All 8 tokens now have full mesh connectivity

New helper addresses:
- arbitrum: 0x56db6AF01e03E31695F847633F0F15eF857D4179
- base: 0x9650deA331801F944335669a8569ed39f7AEf342
- soneium: 0xcA45246445A047670B67F87c5AC75A4DbE5134F1
- mantra: 0xF0d9D232141dF68DD3CE272dd216C8D71645F25A
- linea: 0x3856c8A14cf1D1da69237B8e79aDb4E3eE44fba2
- hyperevm: 0x7e97865321a4a16B7DDc4ea5387E63ED1999b6d7

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 1

🤖 Fix all issues with AI agents
In `@src/consts/warpRoutes.yaml`:
- Around line 112-132: The hyperevm token entry (chainName: hyperevm, symbol:
USDH, name: USD Hyperliquid) is missing a logoURI; add a logoURI field to this
YAML object (alongside addressOrDenom, helperAddress, etc.) with the appropriate
image URL used by other tokens (e.g., the project CDN or repo path) so the UI
can display the token icon; ensure the new key is a string named logoURI and
follows the same formatting/location as other entries in this file.

Comment thread src/consts/warpRoutes.yaml Outdated
Comment on lines +112 to +132
# HyperEVM USDH
- chainName: hyperevm
standard: EvmHypStableSwap
decimals: 6
symbol: USDH
name: USD Hyperliquid
addressOrDenom: '0xc7d07C8acFC66852BAad9Afb49E8A3AA7F6D3575'
collateralAddressOrDenom: '0x111111a1a0667d36bd57c0a9f569b98057111111'
stableSwapPool: mainnet-stableswap-pool
helperAddress: '0x7e97865321a4a16B7DDc4ea5387E63ED1999b6d7'
sUSDAddress: '0x0a089A151228Fd8CdfB1082a12b030D4C064F497'
adapterAddress: '0xc7d07C8acFC66852BAad9Afb49E8A3AA7F6D3575'
connections:
- token: ethereum|arbitrum|0x94C62e7958738B65737a0Db8A5077def3AED84AA
- token: ethereum|arbitrum|0x6BEC839292A36372882Cb850E93FB5aC2A9BA4Af
- token: ethereum|base|0x73Ef899fDa87213e26501707ab585028BFB297c8
- token: ethereum|base|0x23c51024b19303F1315DbFFA055666aE9B7A0B2c
- token: ethereum|soneium|0x867D428B8FbE196EA4e997e7980623E75ED219a7
- token: ethereum|linea|0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895
- token: ethereum|mantra|0xb9Fc2748D7f4d81cfc2ca78a52f52b4ADf1C4895

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing logoURI for HyperEVM USDH entry.

Unlike the other token entries in this file, the HyperEVM USDH configuration doesn't have a logoURI field. This might leave users staring at a blank spot where an icon should be—nobody wants that kind of emptiness in their swamp.

🔧 Proposed fix to add the missing logoURI
   # HyperEVM USDH
   - chainName: hyperevm
     standard: EvmHypStableSwap
     decimals: 6
     symbol: USDH
     name: USD Hyperliquid
     addressOrDenom: '0xc7d07C8acFC66852BAad9Afb49E8A3AA7F6D3575'
     collateralAddressOrDenom: '0x111111a1a0667d36bd57c0a9f569b98057111111'
+    logoURI: <ADD_APPROPRIATE_LOGO_URI_HERE>
     stableSwapPool: mainnet-stableswap-pool
🤖 Prompt for AI Agents
In `@src/consts/warpRoutes.yaml` around lines 112 - 132, The hyperevm token entry
(chainName: hyperevm, symbol: USDH, name: USD Hyperliquid) is missing a logoURI;
add a logoURI field to this YAML object (alongside addressOrDenom,
helperAddress, etc.) with the appropriate image URL used by other tokens (e.g.,
the project CDN or repo path) so the UI can display the token icon; ensure the
new key is a string named logoURI and follows the same formatting/location as
other entries in this file.

- Default registry to stableswap branch for preview deployments
- Whitelist sUSD/stableswap-pool route from registry
- Clear local warpRoutes.yaml (routes come from registry)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove env var fallback to ensure this preview branch always uses
the stableswap-extension-deploy registry branch.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE Just don't

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants