Skip to content

Conversation

@alecande11
Copy link
Contributor

@alecande11 alecande11 commented Dec 8, 2025

Summary by CodeRabbit

  • New Features

    • Modal-driven Deposit & Withdraw flows with new routes, completion screens, and example app buttons to open them.
    • Global asset/balance discovery, on-completion callback for transfers, selectable gas/fee estimation, and unified connected-wallet icon helper.
  • UI/UX Improvements

    • Animated height transitions, modal presentation, refined multi-step asset selection and field layouts, refreshed navigation icons, improved numeric/quantity input behavior, and various new styling modules for deposit/withdraw screens.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

Walkthrough

Adds modal-driven multi-step Deposit and Withdraw flows with new pages, hooks, components, styles, modal state/component and provider integration, fee estimation and submission updates (BridgeTxResult/onCompleted), wallet-icon hook, AnimatedHeight/input improvements, skip-assets/all-balances queries, and Vite example buttons.

Changes

Cohort / File(s) Summary
Pages & Provider
packages/interwovenkit-react/src/pages/deposit/Deposit.tsx, packages/interwovenkit-react/src/pages/deposit/Withdraw.tsx, packages/interwovenkit-react/src/public/app/Routes.tsx, packages/interwovenkit-react/src/public/app/InterwovenKitProvider.tsx
Add multi-step Deposit/Withdraw pages, route entries, and integrate Modal into provider; switch skip-asset sourcing to useAllSkipAssets.
Deposit / Withdraw UI components
packages/interwovenkit-react/src/pages/deposit/*.tsx (e.g., DepositFields.tsx, WithdrawFields.tsx, SelectLocalAsset.tsx, SelectExternalAsset.tsx, TransferCompleted.tsx, TransferTxDetails.tsx, TransferFooter.tsx, FooterWithTxFee.tsx)
New components for asset selection, amount input, route queries, gas simulation, fee selection, transfer details, footers, and completion views.
Deposit hooks & balances
packages/interwovenkit-react/src/pages/deposit/hooks.ts, packages/interwovenkit-react/src/pages/deposit/*
Add useAllBalancesQuery, asset option helpers, useTransferForm, and related types driving deposit/withdraw flows.
Bridge tx & fee flow
packages/interwovenkit-react/src/pages/bridge/data/tx.ts, packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx, packages/interwovenkit-react/src/pages/bridge/data/simulate.ts, packages/interwovenkit-react/src/pages/deposit/FooterWithTxFee.tsx, packages/interwovenkit-react/src/pages/deposit/TransferFooter.tsx
Introduce BridgeTxResult type; extend useBridgeTx with customFee and onCompleted; update BridgePreviewFooter props/label/error handling; gate simulate queries; add gas simulation and fee-selection UI; conditional submission callbacks.
All-skip assets & query keys
packages/interwovenkit-react/src/pages/bridge/data/assets.ts, packages/interwovenkit-react/src/pages/bridge/data/skip.ts
Add useAllSkipAssets() to fetch/cache fungible assets and new skip query keys allAssets and allBalances.
Modal state & component
packages/interwovenkit-react/src/data/ui.ts, packages/interwovenkit-react/src/public/app/Modal.tsx, packages/interwovenkit-react/src/public/app/Modal.module.css
Add useModal atom/API with mutual exclusion to drawer, openModal/closeModal, and a Modal component + styles.
Public API & examples
packages/interwovenkit-react/src/public/data/hooks.ts, examples/vite/src/App.tsx, examples/vite/src/Deposit.tsx, examples/vite/src/Withdraw.tsx
useInterwovenKit exposes openDeposit/openWithdraw and includes modal state in isOpen; Vite example adds Deposit/Withdraw components and buttons.
AnimatedHeight & inputs
packages/interwovenkit-react/src/components/AnimatedHeight.tsx, packages/interwovenkit-react/src/components/form/QuantityInput.tsx, packages/interwovenkit-react/src/components/form/NumericInput.tsx
AnimatedHeight uses ResizeObserver and init guard; QuantityInput accepts optional className; NumericInput refactored to controlled input with unified sanitization and paste handling.
Wallet icon & header
packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts, packages/interwovenkit-react/src/public/app/WidgetHeader.tsx
Add useConnectedWalletIcon hook and update WidgetHeader to use it for connected wallet icon rendering.
Styling modules
packages/interwovenkit-react/src/pages/deposit/*.module.css, packages/interwovenkit-react/src/public/app/Modal.module.css, packages/interwovenkit-react/src/components/form/AssetOptions.module.css
Add multiple CSS modules for deposit/withdraw pages and modal; adjust asset item hover color.
Package manifest
packages/interwovenkit-react/package.json
Replace @initia/icons-react semver with local tarball reference file:../../initia-icons-react-1.0.0.tgz.
Examples: Vite app
examples/vite/src/Deposit.tsx, examples/vite/src/Withdraw.tsx, examples/vite/src/App.tsx
New example components that call openDeposit/openWithdraw; App imports and renders them alongside existing Send/Bridge controls.
Minor UI tweaks
packages/interwovenkit-react/src/pages/wallet/tabs/Home.tsx, packages/interwovenkit-react/src/components/form/AssetOptions.module.css
Swap navigation icons (send/bridge) and tweak asset item hover background.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant App
    participant Modal
    participant Form as TransferForm
    participant Assets
    participant Route as RouteQuery
    participant Fee as FeeEstimator
    participant Bridge as Bridge/Skip

    User->>App: click openDeposit/openWithdraw
    App->>Modal: openModal(path, options)
    Modal->>Form: mount Deposit/Withdraw form
    User->>Form: select local asset
    Form->>Assets: fetch external options & balances
    Assets-->>Form: return assets & balances
    User->>Form: enter quantity
    Form->>Route: debounce -> query routes
    Route-->>Form: return route & pricing
    User->>Form: confirm transfer
    Form->>Fee: simulate / estimate gas
    Fee-->>Form: return gas / fee options
    Form->>Bridge: submitTx/requestTx with chosen fee
    Bridge-->>Form: tx result (txhash / status)
    Form->>Modal: render TransferCompleted view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped into modals wide and bright,
Picked chains and tokens in the soft moonlight,
Fees hummed like crickets, routes found their tune,
I clicked confirm — the tx danced to the moon,
🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat: deposit modal implementation' directly and accurately summarizes the primary change: adding a deposit modal feature with supporting withdrawal functionality, new hooks, styling, and routing updates.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 7cdc2d2 and cb7029a.

📒 Files selected for processing (4)
  • packages/interwovenkit-react/src/components/Page.tsx
  • packages/interwovenkit-react/src/pages/deposit/TransferCompleted.module.css
  • packages/interwovenkit-react/src/pages/deposit/TransferCompleted.tsx
  • packages/interwovenkit-react/src/public/app/Routes.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/interwovenkit-react/src/pages/deposit/TransferCompleted.module.css
  • packages/interwovenkit-react/src/pages/deposit/TransferCompleted.tsx
  • packages/interwovenkit-react/src/public/app/Routes.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (2)
packages/interwovenkit-react/src/components/Page.tsx (2)

2-2: The implementation is correct. The useHistory() hook returns an array of HistoryEntry objects, which inherently has a .length property in JavaScript. The navigation logic history.length > 1 ? -1 : "/" properly handles both cases: navigating back when history exists, and navigating home when the user has no history to return to.


20-24: This comment is based on incorrect assumptions about how the deposit/withdraw flows are implemented. The Deposit and Withdraw components do not use the Page component; instead, they manage navigation entirely through form state (e.g., setValue("page", "select-external")). Therefore, the navigation change does not impact these flows.

The change itself is a reasonable bug fix: it prevents navigating to -1 (go back) when history.length <= 1, falling back to "/" instead. This addresses edge cases where users navigate directly to routes without history or have minimal navigation stack, which can occur in various parts of the app (SendFields, Receive, BridgePreview, etc.) but not in the modal deposit/withdraw flows mentioned in the comment.

Likely an incorrect or invalid review comment.


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

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 8, 2025

Deploying interwovenkit-staging with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb7029a
Status: ✅  Deploy successful!
Preview URL: https://4f3433dd.interwovenkit-staging.pages.dev
Branch Preview URL: https://feat-deposit-withdraw.interwovenkit-staging.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 8, 2025

Deploying interwovenkit-testnet with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb7029a
Status: ✅  Deploy successful!
Preview URL: https://d99549c6.interwovenkit-testnet.pages.dev
Branch Preview URL: https://feat-deposit-withdraw.interwovenkit-testnet.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 8, 2025

Deploying interwovenkit with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb7029a
Status: ✅  Deploy successful!
Preview URL: https://fe1a0175.interwovenkit.pages.dev
Branch Preview URL: https://feat-deposit-withdraw.interwovenkit.pages.dev

View logs

@alecande11 alecande11 marked this pull request as ready for review December 11, 2025 08:41
Copy link

@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: 11

🧹 Nitpick comments (15)
packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.module.css (1)

1-119: Consider adding WebKit scrollbar-hiding rule for .list

You already hide the scrollbar via scrollbar-width and -moz-scrollbars-none; if you also want this in WebKit, consider adding:

.list::-webkit-scrollbar {
  display: none;
}

This keeps behavior consistent across browsers.

packages/interwovenkit-react/src/data/ui.ts (1)

60-79: Avoid double “Widget Closed” analytics on disconnect

useDisconnect calls both closeDrawer() and closeModal(), each of which tracks "Widget Closed". If only one surface is visible, this still emits two close events, which may skew analytics. Consider a shared closeWidget() helper that:

  • Closes both atoms, but
  • Emits "Widget Closed" only once based on (isDrawerOpen || isModalOpen).
packages/interwovenkit-react/src/pages/deposit/hooks.ts (1)

10-38: Overall hook design is cohesive; consider minor reuse to reduce duplication

The deposit hooks are nicely layered (form → selected asset → candidate chains → filtered assets), and the useAllBalancesQuery query shape matches how you consume it.

There’s some repeated logic filtering out external Cosmos chains (chain.chain_type === "cosmos" && chain.bech32_prefix !== "init") in both useDepositAssets and useFilteredDepositAssets. If this grows further, you might want a small helper (e.g. isSupportedDepositChain(chain)) to centralize that condition and avoid drift.

This is non-blocking; the current implementation is clear and correct aside from the balance indexing issue above.

Also applies to: 41-61, 63-107, 109-119

packages/interwovenkit-react/src/public/app/WidgetHeader.tsx (1)

9-9: Header wallet icon handling is fine; consider a fallback when no icon is available

Switching to useConnectedWalletIcon and gating the header on address simplifies the component and keeps it aligned with the global connection state from useInterwovenKit.

One small robustness improvement: useConnectedWalletIcon can return undefined, and Image is currently always rendered with src={icon}. If there’s ever a connector without an icon, you may want to:

{icon && <Image src={icon} width={18} height={18} />}

or provide a default placeholder icon to avoid layout shifts.

Non-blocking; the current implementation will still work in most cases.

Also applies to: 16-18, 59-61, 64-75

examples/vite/src/Deposit.tsx (1)

1-16: Example Deposit component correctly wires to useInterwovenKit

The component cleanly uses useInterwovenKit’s address and openDeposit, returning null when disconnected and triggering the modal on click. This is a good minimal example of how consumers should integrate the new deposit flow.

You could slightly simplify the handler to onClick={openDeposit} instead of onClick={() => openDeposit()}, but that’s purely stylistic.

examples/vite/src/Providers.tsx (1)

46-52: Confirm depositOptions chain IDs for testnet/mainnet setups

Passing depositOptions directly into InterwovenKitProvider is the right place to expose these, and the structure matches the expected { denom, chainId } shape.

However, both entries are hard-coded to chainId: "interwoven-1" and are applied regardless of isTestnet. Since you already toggle other config via isTestnet ? TESTNET : {}, it’s worth double-checking that:

  • These options are valid for your mainnet AND testnet environments, or
  • You intentionally only want them on mainnet (in which case, you may want to conditionally set them, or derive them from the same config used inside the library to avoid drift with that long IBC denom).

Functionally this is fine, but configuration mismatches here would only surface at runtime when trying to deposit.

packages/interwovenkit-react/src/public/app/Modal.module.css (1)

10-25: Consider adding responsive width and overflow handling.

The modal has a fixed width: 400px which may cause issues on smaller viewports (mobile devices). Additionally, consider adding overflow-y: auto to handle content that exceeds max-height.

 .modal {
   position: fixed;
   top: 50%;
   left: 50%;
   z-index: 9999;

-  width: 400px;
+  width: min(400px, calc(100vw - 32px));

   background-color: var(--gray-9);
   border: 1px solid var(--gray-6);
   border-radius: 20px;
   font-family: var(--font-family);
   max-height: 90vh;
+  overflow-y: auto;
   padding: 16px 20px;
   transform: translate(-50%, -50%);
 }
packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.tsx (1)

17-39: Handle empty options and missing logo gracefully.

Two potential UX issues:

  1. If options is empty (no matching assets), the user sees only the title with no feedback.
  2. logo_uri is optional in RouterAsset - if undefined, the image will fail to load.
   return (
     <>
       <h3 className={styles.title}>Select an asset to receive</h3>
       <div className={styles.list}>
-        {options.map(({ denom, chain_id, symbol, logo_uri }) => (
+        {options.length === 0 && <p>No deposit options available</p>}
+        {options.map(({ denom, chain_id, symbol, logo_uri }) => (
           <button
             className={styles.asset}
             key={`${denom}-${chain_id}`}
             onClick={() => {
               setValue("dstDenom", denom)
               setValue("dstChainId", chain_id)
               // reset other values
               setValue("quantity", "")
               setValue("srcDenom", "")
               setValue("srcChainId", "")
             }}
           >
-            <img src={logo_uri} alt={symbol} />
+            {logo_uri && <img src={logo_uri} alt={symbol} />}
             {symbol}
           </button>
         ))}
       </div>
     </>
   )
packages/interwovenkit-react/src/public/app/Modal.tsx (1)

14-24: Backdrop click may trigger closeModal twice.

Both onOpenChange (line 14) and the backdrop's onClick (line 16) call closeModal(). When clicking the backdrop, the Dialog component may fire onOpenChange internally, resulting in duplicate close calls.

Consider removing the explicit onClick on the backdrop if the Dialog handles it:

     <Dialog.Root open={isModalOpen} onOpenChange={(isOpen) => !isOpen && closeModal()}>
       <Dialog.Portal container={portalContainer}>
-        <Dialog.Backdrop className={styles.backdrop} onClick={closeModal} />
+        <Dialog.Backdrop className={styles.backdrop} />
         <Dialog.Popup className={clsx(styles.modal)}>

Also, clsx(styles.modal) on line 17 can be simplified to just styles.modal since there's only one class.

packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (2)

60-67: useMemo implicitly returns undefined when validation passes.

The function doesn't explicitly return for the valid case, relying on implicit undefined. While this works, it's clearer to be explicit. Also, the trailing comment suggests incomplete work.

   const disabledMessage = useMemo(() => {
     if (!srcAsset) return "Select asset"
     if (!quantity) return "Enter amount"
     if (errors.quantity) return errors.quantity.message
     if (routeError) return "No route found"
-
-    // Destructure error fields in deps to properly track each field change
+    return undefined
   }, [quantity, errors.quantity, srcAsset, routeError])

143-157: Duplicate formatAmount calls; extract to a variable.

formatAmount(balance, { decimals: srcAsset?.decimals || 6 }) is called three times with identical arguments. Extract to a variable for clarity and minor performance improvement.

+          const formattedBalance = formatAmount(balance, { decimals: srcAsset?.decimals || 6 })
           <button
             className={styles.maxButton}
             onClick={() => {
-              if (
-                Number(quantity) ===
-                Number(formatAmount(balance, { decimals: srcAsset?.decimals || 6 }))
-              )
-                return
-
-              setValue("quantity", formatAmount(balance, { decimals: srcAsset?.decimals || 6 }))
+              if (quantity === formattedBalance) return
+              setValue("quantity", formattedBalance)
             }}
           >
-            <IconWallet /> {formatAmount(balance, { decimals: srcAsset?.decimals || 6 })}{" "}
+            <IconWallet /> {formattedBalance}{" "}
             <span>MAX</span>
           </button>

Note: Move formattedBalance calculation outside the JSX, inside the component body where balance is checked.

packages/interwovenkit-react/src/pages/deposit/FooterWithTxFee.tsx (1)

24-57: Query key includes entire tx object; consider using a stable identifier.

Including the entire tx object in queryKey may cause unnecessary refetches if the object reference changes even when the content is the same. If tx has a unique identifier or hash, prefer using that.

   const { data: gasEstimate, isLoading } = useQuery({
-    queryKey: ["tx-gas-estimate", tx, address, srcChainId],
+    queryKey: ["tx-gas-estimate", JSON.stringify(tx.cosmos_tx?.msgs), address, srcChainId],

Alternatively, if a transaction hash or identifier is available, use that instead for a more stable key.

packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1)

57-64: Consider adding explicit dimensions to wallet icon.

The wallet icon image lacks explicit width and height attributes, which could cause layout shift during loading.

Apply this diff:

-<img src={walletIcon} alt="Wallet" /> {truncate(address)}
+<img src={walletIcon} alt="Wallet" width="16" height="16" /> {truncate(address)}
packages/interwovenkit-react/src/pages/deposit/DepositFooter.tsx (1)

52-78: Consider memoizing getFeeDetails for performance.

The getFeeDetails function performs BigNumber calculations and is called multiple times in getInitialFeeDenom (lines 83, 88), potentially in a loop (line 88). This could impact performance when there are many fee options.

Consider memoizing the results:

const feeDetailsCache = useMemo(() => {
  const cache = new Map<string, ReturnType<typeof getFeeDetails>>()
  return (feeDenom: string) => {
    if (cache.has(feeDenom)) return cache.get(feeDenom)!
    const details = getFeeDetails(feeDenom)
    cache.set(feeDenom, details)
    return details
  }
}, [balances, feeCoins, srcAsset, srcDenom, quantity, findAsset])

Or compute all fee details once upfront if the number of options is small.

packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (1)

1-8: Consider adding z-index and responsive behavior to fixed back button.

The fixed positioning without a z-index could cause stacking issues with other fixed elements. Additionally, the hardcoded top: 16px; left: 20px may not adapt well to different viewport sizes or modal contexts.

Consider adding:

 .back {
   position: fixed;
   top: 16px;
   left: 20px;
+  z-index: 10;
 
   color: var(--gray-4);
   padding: 3px;
 }
+
+@media (max-width: 768px) {
+  .back {
+    top: 12px;
+    left: 12px;
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 02307bf and 2c5c5fd.

⛔ Files ignored due to path filters (3)
  • packages/interwovenkit-react/src/pages/deposit/assets/Completed.webm is excluded by !**/*.webm
  • packages/interwovenkit-react/src/pages/deposit/assets/Failed.svg is excluded by !**/*.svg
  • packages/interwovenkit-react/src/pages/deposit/assets/Loading.webm is excluded by !**/*.webm
📒 Files selected for processing (31)
  • examples/vite/src/App.tsx (2 hunks)
  • examples/vite/src/Deposit.tsx (1 hunks)
  • examples/vite/src/Providers.tsx (1 hunks)
  • packages/interwovenkit-react/src/components/form/QuantityInput.tsx (2 hunks)
  • packages/interwovenkit-react/src/data/config.ts (2 hunks)
  • packages/interwovenkit-react/src/data/ui.ts (2 hunks)
  • packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts (1 hunks)
  • packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/bridge/data/assets.ts (2 hunks)
  • packages/interwovenkit-react/src/pages/bridge/data/skip.ts (1 hunks)
  • packages/interwovenkit-react/src/pages/bridge/data/tx.ts (6 hunks)
  • packages/interwovenkit-react/src/pages/deposit/Deposit.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositCompleted.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositCompleted.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositFooter.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/FooterWithTxFee.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/hooks.ts (1 hunks)
  • packages/interwovenkit-react/src/public/app/InterwovenKitProvider.tsx (3 hunks)
  • packages/interwovenkit-react/src/public/app/Modal.module.css (1 hunks)
  • packages/interwovenkit-react/src/public/app/Modal.tsx (1 hunks)
  • packages/interwovenkit-react/src/public/app/Routes.tsx (2 hunks)
  • packages/interwovenkit-react/src/public/app/WidgetHeader.tsx (2 hunks)
  • packages/interwovenkit-react/src/public/data/constants.ts (1 hunks)
  • packages/interwovenkit-react/src/public/data/hooks.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (1)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (3)
  • useFilteredDepositAssets (87-107)
  • useDepositForm (117-119)
  • useDstDepositAsset (41-50)
packages/interwovenkit-react/src/public/app/Modal.tsx (1)
packages/interwovenkit-react/src/data/ui.ts (1)
  • useModal (35-58)
examples/vite/src/App.tsx (1)
packages/interwovenkit-react/src/pages/deposit/Deposit.tsx (1)
  • Deposit (8-19)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (3)
packages/interwovenkit-react/src/pages/bridge/data/skip.ts (2)
  • useSkip (20-23)
  • skipQueryKeys (7-18)
packages/interwovenkit-react/src/pages/bridge/data/chains.ts (1)
  • useSkipChains (38-54)
packages/interwovenkit-react/src/data/http.ts (1)
  • STALE_TIMES (4-8)
packages/interwovenkit-react/src/pages/deposit/FooterWithTxFee.tsx (3)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)
  • useBridgePreviewState (53-55)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInitiaAddress (16-20)
packages/interwovenkit-react/src/data/signer.ts (2)
  • useAminoTypes (43-46)
  • useCreateSigningStargateClient (239-265)
packages/interwovenkit-react/src/public/app/WidgetHeader.tsx (3)
packages/interwovenkit-react/src/data/ui.ts (1)
  • useDisconnect (60-80)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInterwovenKit (51-99)
packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts (1)
  • useConnectedWalletIcon (3-11)
packages/interwovenkit-react/src/public/app/InterwovenKitProvider.tsx (1)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (1)
  • useAllSkipAssets (15-43)
examples/vite/src/Deposit.tsx (2)
packages/interwovenkit-react/src/pages/deposit/Deposit.tsx (1)
  • Deposit (8-19)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInterwovenKit (51-99)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
packages/interwovenkit-react/src/data/ui.ts (2)
  • useDrawer (10-33)
  • useModal (35-58)
packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.tsx (3)
packages/interwovenkit-react/src/data/config.ts (1)
  • useConfig (38-42)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (1)
  • useAllSkipAssets (15-43)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (2)
  • useDepositForm (117-119)
  • useAllBalancesQuery (10-39)
packages/interwovenkit-react/src/data/ui.ts (1)
packages/interwovenkit-react/src/data/analytics.ts (1)
  • useAnalyticsTrack (39-53)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInterwovenKit (51-99)
packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (1)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)
  • useBridgeTx (62-272)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (21)
packages/interwovenkit-react/src/data/config.ts (1)

7-10: DepositOption and Config.depositOptions typing look consistent

The new DepositOption shape and optional depositOptions field on Config are straightforward, fit the existing config model, and align with the MAINNET configuration usage.

Also applies to: 27-34

packages/interwovenkit-react/src/pages/bridge/data/skip.ts (1)

7-18: New skip query keys integrate cleanly with existing key factory usage

allAssets and allBalances follow the existing key patterns and should work fine with React Query’s key hashing and the createQueryKeys namespace.

packages/interwovenkit-react/src/components/form/QuantityInput.tsx (1)

16-20: className extension on QuantityInput is safe and flexible

Threading className through and combining it with styles.input via clsx keeps existing styling while allowing per‑use overrides. Validation and form wiring remain unchanged.

Also applies to: 22-48

packages/interwovenkit-react/src/public/data/constants.ts (1)

6-20: MAINNET.depositOptions matches the new Config surface

The depositOptions entries for MAINNET conform to DepositOption and keep TESTNET untouched, so this is a backwards-compatible configuration extension.

packages/interwovenkit-react/src/data/ui.ts (1)

2-3: Drawer and modal atoms are modeled cleanly with mutual exclusivity

isModalOpenAtom plus useModal mirror the existing drawer behavior, and explicitly closing the other surface before opening ensures only one of drawer or modal is active at a time. The analytics hooks are reused consistently.

Also applies to: 7-9, 10-33, 35-58

packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)

57-60: useBridgeTx options (customFee + navigateTo) are wired in a backwards-compatible way

The UseBridgeTxOptions shape and useBridgeTx(tx, options) signature preserve existing behavior when options is omitted, while allowing:

  • A customFee path for Initia via submitTxSync.
  • Existing requestTxSync usage when no customFee is supplied.
    This keeps the non-Initia and default flows unchanged.

Also applies to: 62-63, 73-76, 102-119

packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (1)

1-22: BridgePreviewFooter correctly forwards optional fee and navigation options

Extending props with fee and navigateTo and passing them through as { customFee: fee, navigateTo } to useBridgeTx preserves existing behavior while enabling the new custom-fee and post-tx navigation flows.

packages/interwovenkit-react/src/public/data/hooks.ts (1)

10-10: Modal integration and isOpen semantics look consistent

Using both useDrawer and useModal here, exposing openDeposit via openModal("/deposit"), and redefining isOpen as isDrawerOpen || isModalOpen aligns with the drawer/modal coordination already present in useDisconnect. This should keep the public API intuitive (single “open” flag for either surface) while enabling the new deposit modal.

Just be aware this slightly broadens the meaning of isOpen for existing consumers; if any caller depended on it representing the drawer only, they’ll now see it true when the deposit modal is open as well.

Also applies to: 60-61, 75-77, 90-95

packages/interwovenkit-react/src/public/app/Routes.tsx (1)

11-12: Deposit routes are wired consistently with existing routing

Importing Deposit / DepositCompleted and handling /deposit and /deposit/completed in the switch fits the existing pattern (e.g., bridge/history/op routes). The prior if (!address) guard still ensures we never render these pages when disconnected, and the address-change useEffect only normalizes bridge/NFT paths, so deposit paths aren’t unintentionally redirected.

No issues from a routing/control-flow perspective.

Also applies to: 95-98

examples/vite/src/App.tsx (1)

5-5: Example app integration of Deposit is straightforward and safe

Adding <Deposit /> next to <Send /> and <Bridge /> is fine here: the Deposit example component already returns null when there’s no address, so this doesn’t alter behavior for disconnected users and keeps the example simple.

Looks good as an integration point for the new modal flow.

Also applies to: 31-34

packages/interwovenkit-react/src/pages/deposit/DepositCompleted.module.css (1)

1-46: Deposit completion styles align with existing design patterns

The container, typography, spacing, and error styles are consistent with the rest of the kit’s CSS modules (use of var(--gray-*) and var(--error), small font sizes, centered layout). Nothing problematic from a layout or theming perspective.

No changes needed here.

packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts (1)

1-11: LGTM!

Clean implementation. The early return for missing connector and the Privy-specific icon handling are appropriate.

packages/interwovenkit-react/src/public/app/InterwovenKitProvider.tsx (1)

82-87: Verify that rendering <Routes /> in both Modal and Drawer works as intended.

Both Modal and Drawer now wrap <Routes />. Ensure that the routing state (via MemoryRouter) and component mounts behave correctly when only one container is visible at a time, and that there are no duplicate side effects from both route trees being mounted simultaneously.

packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css (1)

1-33: LGTM!

Clean and well-structured CSS module with appropriate use of flexbox and consistent spacing.

packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1)

1-11: LGTM!

Imports are well-organized and appropriate for the component's functionality.

packages/interwovenkit-react/src/pages/deposit/DepositFooter.tsx (4)

40-48: LGTM! Fee calculation logic is sound.

The fee calculation correctly:

  • Applies gas adjustment with Math.ceil to avoid underestimation
  • Only calculates fees for Cosmos transactions
  • Maps over gas prices to generate multiple fee options

80-94: LGTM! Fee denomination selection logic is well-structured.

The function appropriately prioritizes:

  1. Last used denomination (if sufficient)
  2. First sufficient denomination
  3. First available denomination (fallback)

Since this is called in a useState initializer (line 96), it only executes once during initial render.


114-146: LGTM! Fee rendering logic handles both single and multiple options elegantly.

The function appropriately:

  • Returns null when no options exist
  • Displays a simple label for single options
  • Renders a dropdown for multiple options with proper formatting

1-17: The imports and API usage are compatible with @cosmjs/stargate v0.36.2. Both calculateFee and GasPrice.fromString are correctly exported and used according to the library's documented API—specifically on line 45 where GasPrice.fromString(amount + denom) produces the expected format (e.g., "0.0025uatom").

packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (2)

54-64: LGTM! Chain icon positioning creates proper badge effect.

The absolute positioning with right: -4px intentionally extends the chain icon beyond the parent asset icon boundary to create an overlapping badge effect, which is a common UI pattern. The parent .assetIcon has position: relative (line 45) to establish the positioning context.


10-165: LGTM! Well-structured CSS with good use of custom properties.

The styles are well-organized with:

  • Semantic class names
  • Proper use of CSS custom properties for theming
  • Appropriate layout techniques (flexbox, grid)
  • Consistent spacing and sizing

Comment on lines +25 to +38
select: ({ chain_to_assets_map }) => {
const allAssets: RouterAsset[] = []

for (const chainId in chain_to_assets_map) {
if (!chains.find((chain) => chain.chain_id === chainId)) continue
const { assets } = chain_to_assets_map[chainId] ?? { assets: [] }
queryClient.setQueryData(skipQueryKeys.assets(chainId).queryKey, { chain_to_assets_map })
allAssets.push(...assets)

for (const asset of assets) {
queryClient.setQueryData(skipQueryKeys.asset(chainId, asset.denom).queryKey, asset)
}
}
return allAssets
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Side effects in select function and incorrect cache data.

Two issues:

  1. Side effects in select: The select function should be pure. Calling queryClient.setQueryData() during selection can cause unpredictable behavior with React Query's rendering cycle. Move cache population to onSuccess or a useEffect.

  2. Line 31 caches incorrect data: You're caching the entire chain_to_assets_map for each chain's query key, but useSkipAssets expects only that chain's subset. This could cause inconsistencies when useSkipAssets reads from the cache.

 export function useAllSkipAssets() {
   const skip = useSkip()
   const chains = useSkipChains()
   const queryClient = useQueryClient()
+  const chainIdSet = new Set(chains.map((c) => c.chain_id))
+
   const { data } = useSuspenseQuery({
     queryKey: skipQueryKeys.allAssets().queryKey,
     queryFn: () =>
       skip
         .get("v2/fungible/assets")
         .json<{ chain_to_assets_map: Partial<Record<string, { assets: RouterAsset[] }>> }>(),
     select: ({ chain_to_assets_map }) => {
       const allAssets: RouterAsset[] = []

       for (const chainId in chain_to_assets_map) {
-        if (!chains.find((chain) => chain.chain_id === chainId)) continue
+        if (!chainIdSet.has(chainId)) continue
         const { assets } = chain_to_assets_map[chainId] ?? { assets: [] }
-        queryClient.setQueryData(skipQueryKeys.assets(chainId).queryKey, { chain_to_assets_map })
         allAssets.push(...assets)
-
-        for (const asset of assets) {
-          queryClient.setQueryData(skipQueryKeys.asset(chainId, asset.denom).queryKey, asset)
-        }
       }
       return allAssets
     },
     staleTime: STALE_TIMES.MINUTE,
   })
+
+  // Populate cache outside of select
+  useEffect(() => {
+    if (!data) return
+    // Re-fetch response or derive from data to populate individual caches
+  }, [data, queryClient])
+
   return data
 }

Note: This diff shows the direction - you'll need to restructure to access chain_to_assets_map outside select for proper cache population.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/bridge/data/assets.ts around lines
25–38, the select function currently performs side effects (calling
queryClient.setQueryData) and caches the entire chain_to_assets_map for each
chain key—both wrong; remove all queryClient.setQueryData calls from the select
to keep it pure, and instead access the fetched chain_to_assets_map in the
query's onSuccess (or a useEffect) where you should iterate over
chain_to_assets_map, for each chainId set the per-chain cache to only that
chain's assets (not the whole map) and set per-asset cache entries for each
asset; ensure select only returns the aggregated allAssets array and that cache
writes use the correct subset shape expected by useSkipAssets.

Comment on lines 4 to 11
export interface FormValues {
quantity: string
}

export const Deposit = () => {
const form = useForm<FormValues>({
mode: "onChange",
defaultValues: { quantity: "" },
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

FormValues interface is incomplete; missing fields used elsewhere.

The FormValues interface only declares quantity, but DepositFields.tsx and SelectSrcAsset.tsx call setValue for srcDenom, srcChainId, dstDenom, and dstChainId. This type mismatch will cause TypeScript errors or lose type safety.

 export interface FormValues {
+  srcDenom: string
+  srcChainId: string
+  dstDenom: string
+  dstChainId: string
   quantity: string
 }

 export const Deposit = () => {
   const form = useForm<FormValues>({
     mode: "onChange",
-    defaultValues: { quantity: "" },
+    defaultValues: {
+      srcDenom: "",
+      srcChainId: "",
+      dstDenom: "",
+      dstChainId: "",
+      quantity: "",
+    },
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface FormValues {
quantity: string
}
export const Deposit = () => {
const form = useForm<FormValues>({
mode: "onChange",
defaultValues: { quantity: "" },
export interface FormValues {
srcDenom: string
srcChainId: string
dstDenom: string
dstChainId: string
quantity: string
}
export const Deposit = () => {
const form = useForm<FormValues>({
mode: "onChange",
defaultValues: {
srcDenom: "",
srcChainId: "",
dstDenom: "",
dstChainId: "",
quantity: "",
},
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/Deposit.tsx around lines 4–11,
the FormValues interface only declares quantity but other components call
setValue for srcDenom, srcChainId, dstDenom, and dstChainId; add those
properties to FormValues (e.g., srcDenom: string; srcChainId: string; dstDenom:
string; dstChainId: string) and update the useForm defaultValues to include
initial values for these keys so TypeScript types align with setValue calls and
remain type-safe.

Comment on lines 42 to 54
export function DepositCompleted() {
const state = useLocationState<SuccessState | ErrorState>()
const { route, values } = useBridgePreviewState()
const { closeModal } = useModal()

const isError = "error" in state
const txHash = !isError ? state.txHash : ""
const chainId = !isError ? state.chainId : ""
const timestamp = ("timestamp" in state ? state.timestamp : undefined) ?? 0

// Get source asset information for display
const { srcDenom, srcChainId, quantity } = values
const srcAsset = useSkipAsset(srcDenom, srcChainId)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing null checks for state, route, and srcAsset can cause runtime crashes.

If a user navigates directly to this page or if data is unavailable:

  • state could be undefined (line 43)
  • route from useBridgePreviewState() could be undefined (accessed on line 93)
  • srcAsset from useSkipAsset() could be null/undefined (accessed on line 104)

Consider adding early returns or fallbacks:

 export function DepositCompleted() {
   const state = useLocationState<SuccessState | ErrorState>()
   const { route, values } = useBridgePreviewState()
   const { closeModal } = useModal()

+  // Guard against missing state (e.g., direct navigation)
+  if (!state || !route || !values) {
+    return (
+      <div className={styles.container}>
+        <h3 className={styles.title}>Invalid state</h3>
+        <Button.White fullWidth onClick={closeModal}>
+          Close
+        </Button.White>
+      </div>
+    )
+  }
+
   const isError = "error" in state

Also guard srcAsset before use on line 104.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function DepositCompleted() {
const state = useLocationState<SuccessState | ErrorState>()
const { route, values } = useBridgePreviewState()
const { closeModal } = useModal()
const isError = "error" in state
const txHash = !isError ? state.txHash : ""
const chainId = !isError ? state.chainId : ""
const timestamp = ("timestamp" in state ? state.timestamp : undefined) ?? 0
// Get source asset information for display
const { srcDenom, srcChainId, quantity } = values
const srcAsset = useSkipAsset(srcDenom, srcChainId)
export function DepositCompleted() {
const state = useLocationState<SuccessState | ErrorState>()
const { route, values } = useBridgePreviewState()
const { closeModal } = useModal()
// Guard against missing state (e.g., direct navigation)
if (!state || !route || !values) {
return (
<div className={styles.container}>
<h3 className={styles.title}>Invalid state</h3>
<Button.White fullWidth onClick={closeModal}>
Close
</Button.White>
</div>
)
}
const isError = "error" in state
const txHash = !isError ? state.txHash : ""
const chainId = !isError ? state.chainId : ""
const timestamp = ("timestamp" in state ? state.timestamp : undefined) ?? 0
// Get source asset information for display
const { srcDenom, srcChainId, quantity } = values
const srcAsset = useSkipAsset(srcDenom, srcChainId)
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/DepositCompleted.tsx around
lines 42 to 54, the code assumes useLocationState(), useBridgePreviewState() and
useSkipAsset() always return values; add null/undefined guards and fallbacks to
avoid runtime crashes: check if state is falsy before reading "error", txHash,
chainId, and timestamp and return early or render a safe fallback; validate
route from useBridgePreviewState() before accessing any of its properties (line
~93) and handle missing route with an early return or placeholder; guard
srcAsset from useSkipAsset() before using its fields (line ~104) and provide a
fallback asset display or early return. Ensure the checks are minimal and
consistent (e.g. if (!state) return null) and update dependent variables to use
safe defaults when appropriate.

Comment on lines 32 to 34
const minimumReceived =
(BigInt(route.estimated_amount_out) * BigInt(10000 - Number(values.slippagePercent) * 100)) /
BigInt(10000)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add validation for slippagePercent to prevent NaN and overflow.

The calculation assumes slippagePercent is a valid number, but Number(values.slippagePercent) could return NaN if the value is invalid. Additionally, multiplying by 100 before subtraction could theoretically overflow for extreme values.

Consider adding validation:

+const slippage = Number(values.slippagePercent)
+if (isNaN(slippage) || slippage < 0 || slippage > 100) {
+  // Handle invalid slippage
+  return null
+}
+
 const minimumReceived =
-  (BigInt(route.estimated_amount_out) * BigInt(10000 - Number(values.slippagePercent) * 100)) /
+  (BigInt(route.estimated_amount_out) * BigInt(10000 - slippage * 100)) /
   BigInt(10000)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx around
lines 32–34, the slippagePercent is converted with Number(...) without
validation which can produce NaN or allow extreme values that could cause
incorrect math/overflow when turned into BigInt; change the code to parse and
validate slippagePercent first (use parseFloat or Number and check isFinite and
not NaN), clamp it to a safe range (e.g., 0–100), convert to basis points or a
scaled integer safely (e.g., percent * 100) after validation, provide a sensible
default (like 0.5) when invalid, and only then perform the BigInt arithmetic so
the calculation never receives NaN or out-of-range values.

Comment on lines 67 to 73
<div className={styles.detail}>
<p className={styles.detailLabel}>Minimum received</p>
<p className={styles.detailValue}>
<img src={dstAsset.logo_uri} alt={dstAsset.symbol} />{" "}
{formatAmount(minimumReceived, { decimals: dstAsset.decimals })} {dstAsset.symbol}
</p>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit dimensions to asset logo.

The asset logo at line 70 lacks explicit width and height attributes. Consider adding dimensions for consistency and to prevent layout shift:

-<img src={dstAsset.logo_uri} alt={dstAsset.symbol} />{" "}
+<img src={dstAsset.logo_uri} alt={dstAsset.symbol} width="16" height="16" />{" "}

Note: The formatAmount function appears to handle the numeric value correctly based on usage patterns throughout the codebase.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className={styles.detail}>
<p className={styles.detailLabel}>Minimum received</p>
<p className={styles.detailValue}>
<img src={dstAsset.logo_uri} alt={dstAsset.symbol} />{" "}
{formatAmount(minimumReceived, { decimals: dstAsset.decimals })} {dstAsset.symbol}
</p>
</div>
<div className={styles.detail}>
<p className={styles.detailLabel}>Minimum received</p>
<p className={styles.detailValue}>
<img src={dstAsset.logo_uri} alt={dstAsset.symbol} width="16" height="16" />{" "}
{formatAmount(minimumReceived, { decimals: dstAsset.decimals })} {dstAsset.symbol}
</p>
</div>
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx around
lines 67 to 73, the asset logo image element lacks explicit width and height
attributes causing potential layout shift; update the <img> tag to include fixed
width and height (use appropriate pixel values consistent with other asset icons
in the app or a constant from styles), and ensure the alt remains
dstAsset.symbol; if CSS controls sizing, set matching width/height attributes to
match those styles to prevent CLS.

Comment on lines +33 to +40
const messages = tx.cosmos_tx.msgs.map(({ msg_type_url, msg }) => {
if (!(msg_type_url && msg)) throw new Error("Invalid transaction data")
// Note: `typeUrl` comes in proto format, but `msg` is in amino format.
return aminoTypes.fromAmino({
type: aminoConverters[msg_type_url].aminoType,
value: JSON.parse(msg),
})
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing guard for aminoConverters[msg_type_url] lookup.

If msg_type_url is not present in aminoConverters, the lookup returns undefined and accessing .aminoType throws. While inside try/catch, this silently fails without a clear error message.

         const messages = tx.cosmos_tx.msgs.map(({ msg_type_url, msg }) => {
           if (!(msg_type_url && msg)) throw new Error("Invalid transaction data")
+          const converter = aminoConverters[msg_type_url]
+          if (!converter) throw new Error(`Unsupported message type: ${msg_type_url}`)
           // Note: `typeUrl` comes in proto format, but `msg` is in amino format.
           return aminoTypes.fromAmino({
-            type: aminoConverters[msg_type_url].aminoType,
+            type: converter.aminoType,
             value: JSON.parse(msg),
           })
         })
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/FooterWithTxFee.tsx around
lines 33 to 40, the code assumes aminoConverters[msg_type_url] exists and
accesses .aminoType which can be undefined and will cause a confusing failure;
add a guard after validating msg_type_url and msg to check that
aminoConverters[msg_type_url] is defined, and if not throw a clear, descriptive
error (including the msg_type_url) or handle the missing converter appropriately
(e.g., skip the message or provide a fallback) before calling
aminoTypes.fromAmino so failures are explicit and informative.

Copy link

@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: 3

♻️ Duplicate comments (1)
packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (1)

57-60: Address the past review comment about NaN display.

The issue flagged in the previous review remains unresolved. When balance.value_usd is undefined or null, Number(balance.value_usd) returns NaN, displaying $NaN to users.

Apply this diff to handle missing USD values:

             <p className={styles.balance}>
               {formatAmount(balance?.amount, { decimals: balance.decimals || 6 })}
             </p>
-            <p className={styles.value}>${Number(balance.value_usd).toFixed(2)}</p>
+            <p className={styles.value}>
+              {balance.value_usd != null ? `$${Number(balance.value_usd).toFixed(2)}` : "-"}
+            </p>
🧹 Nitpick comments (1)
packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css (1)

12-14: Consider reordering margin properties.

The margin-bottom appears before margin-top, which is unconventional. For better readability, consider placing margin-top first.

-.list {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-
-  margin-bottom: 8px;
-
-  margin-top: 20px;
-}
+.list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: 20px;
+  margin-bottom: 8px;
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between f720ed6 and 9b2944f.

⛔ Files ignored due to path filters (1)
  • packages/interwovenkit-react/src/pages/deposit/assets/Empty.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (1 hunks)
  • packages/interwovenkit-react/src/public/app/Modal.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/interwovenkit-react/src/public/app/Modal.module.css
  • packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.module.css
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (1)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (3)
  • useFilteredDepositAssets (87-107)
  • useDepositForm (117-119)
  • useDstDepositAsset (41-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (7)
packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css (1)

36-42: LGTM!

The circular image styling with fixed dimensions and border is appropriate for asset icons.

packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx (6)

1-5: LGTM!

Imports are clean and properly structured.


7-12: LGTM!

The component correctly retrieves filtered assets and destination asset information. The early return ensures the component only renders when a destination asset is selected, which aligns with the deposit flow requirements.


14-17: LGTM!

The navigateBack function correctly clears the destination asset selection to return to the previous step in the deposit flow.


19-31: LGTM!

The empty state is properly gated by loading state and provides clear feedback to users when no supported assets are available for deposit.


45-49: LGTM!

The onClick handler correctly updates the form with the selected source asset details and clears the quantity field for new input.


68-68: LGTM!

Standard default export.

Comment on lines 17 to 30
.asset {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 8px;

width: 100%;

border: 1px solid var(--gray-5);
border-radius: 12px;
font-size: 14px;
font-weight: 600;
padding: 11px 12px;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add cursor pointer for interactive element.

The .asset class has a hover state, indicating it's interactive, but lacks cursor: pointer. Users expect visual feedback when hovering over clickable elements.

 .asset {
   display: flex;
   justify-content: flex-start;
   align-items: center;
   gap: 8px;
 
   width: 100%;
 
   border: 1px solid var(--gray-5);
   border-radius: 12px;
   font-size: 14px;
   font-weight: 600;
   padding: 11px 12px;
+  cursor: pointer;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.asset {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 8px;
width: 100%;
border: 1px solid var(--gray-5);
border-radius: 12px;
font-size: 14px;
font-weight: 600;
padding: 11px 12px;
}
.asset {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 8px;
width: 100%;
border: 1px solid var(--gray-5);
border-radius: 12px;
font-size: 14px;
font-weight: 600;
padding: 11px 12px;
cursor: pointer;
}
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css
around lines 17 to 30, the .asset class is styled as an interactive element but
lacks a cursor change; add cursor: pointer to the .asset rule so the pointer
cursor appears on hover, keeping existing layout and visual styles unchanged.

Comment on lines +32 to +34
.asset:hover {
background-color: var(--gray-8);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .asset class is interactive but lacks focus styles, preventing keyboard users from navigating the asset selection. This blocks task completion for users who rely on keyboard navigation.

 .asset:hover {
   background-color: var(--gray-8);
 }
+
+.asset:focus-visible {
+  background-color: var(--gray-8);
+  outline: 2px solid var(--primary);
+  outline-offset: 2px;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.asset:hover {
background-color: var(--gray-8);
}
.asset:hover {
background-color: var(--gray-8);
}
.asset:focus-visible {
background-color: var(--gray-8);
outline: 2px solid var(--primary);
outline-offset: 2px;
}
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/SelectDstAsset.module.css
around lines 32 to 34, the interactive .asset rule only styles :hover and lacks
:focus/:focus-visible styles for keyboard users; add focus and focus-visible
selectors matching or complementing the hover state (e.g., outline or
background-color change and visible focus ring using accessible contrast and
offset) and ensure :focus-visible is preferred to avoid double indicators,
keeping styles consistent with :hover and maintaining accessibility (consider
adding :active as well).

Comment on lines 52 to 53
<img src={asset.logo_uri} alt={asset.symbol} className={styles.assetIcon} />
<img src={chain.logo_uri || ""} alt={chain.chain_name} className={styles.chainIcon} />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback image for missing chain logo.

If chain.logo_uri is falsy, the empty string fallback will render a broken image element. Consider using a fallback icon or hiding the image when the URI is unavailable.

Apply this diff to handle missing logos gracefully:

-              <img src={chain.logo_uri || ""} alt={chain.chain_name} className={styles.chainIcon} />
+              {chain.logo_uri && (
+                <img src={chain.logo_uri} alt={chain.chain_name} className={styles.chainIcon} />
+              )}
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/SelectSrcAsset.tsx around
lines 52-53, the chain image currently uses an empty-string fallback which
renders a broken <img>; change this to handle missing logos by either
conditionally rendering the <img> only when chain.logo_uri is truthy or by
supplying a real fallback (e.g., a local/default icon path) and attach an
onError handler to swap to that fallback if loading fails; ensure the alt text
remains meaningful and the JSX uses the chosen conditional/fallback approach so
broken images are not shown.

Copy link

@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: 2

🧹 Nitpick comments (2)
packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (2)

109-120: Consider adding hover states and transitions for better UX.

Interactive buttons like .maxButton and .detailsButton would benefit from hover states and smooth transitions to provide visual feedback and improve the user experience.

Add hover and transition properties:

.maxButton {
  display: flex;
  gap: 4px;

  color: var(--gray-3);
  font-size: 12px;
  font-weight: 500;
  transition: color 0.2s ease;
  cursor: pointer;
}

.maxButton:hover {
  color: var(--gray-1);
}

.detailsButton {
  justify-content: flex-start;
  gap: 2px;

  color: var(--gray-2);
  font-size: 12px;
  font-weight: 500;
  transition: color 0.2s ease;
  cursor: pointer;
}

.detailsButton:hover {
  color: var(--gray-1);
}

Also applies to: 139-146


18-20: Consider using margin instead of a spacer element.

The .divider class serves solely as vertical spacing. This pattern requires an extra DOM element. Consider applying margin-top or margin-bottom to adjacent elements or using gap in flex/grid containers for cleaner markup.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9b2944f and 6d9efb6.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet

Comment on lines 29 to 42
.asset {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;

width: 100%;
height: 56px;

background-color: var(--gray-7);
border-radius: 8px;
column-gap: 8px;
margin-bottom: 20px;
padding: 6px 12px;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

Interactive elements like .asset (asset selector) and .input lack explicit focus indicators. While browsers provide default focus styles, defining consistent, visible focus states is essential for keyboard navigation and accessibility compliance.

Add focus styles to ensure keyboard users can see which element is focused:

.asset:focus-visible {
  outline: 2px solid var(--gray-3);
  outline-offset: 2px;
}

.input:focus {
  outline: 2px solid var(--gray-3);
  outline-offset: -2px;
}

Also applies to: 82-92

🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css
around lines 29-42 (and also apply same change to lines 82-92), the .asset and
.input elements lack explicit keyboard focus styles; add CSS focus rules so
keyboard users see the focused element — add a :focus-visible rule for .asset
(e.g., 2px solid var(--gray-3) with a small outline-offset) and a focus (or
focus-visible) rule for .input with the same visible outline and appropriate
outline-offset to avoid clipping; ensure to use :focus-visible where possible
for better mouse/keyboard differentiation and include both rules in the file at
the indicated lines.

Copy link

@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

♻️ Duplicate comments (1)
packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1)

32-35: Validate slippagePercent before calculation to prevent NaN and runtime errors.

The calculation converts values.slippagePercent using Number() without validation. If the value is invalid, Number() returns NaN, and BigInt(NaN) throws a RangeError. This was flagged in a previous review but remains unresolved.

Apply validation before the calculation:

+ const slippage = Number(values.slippagePercent)
+ if (isNaN(slippage) || slippage < 0 || slippage > 100) {
+   // Handle invalid slippage with a safe default
+   const safeSlippage = 0.5
+   const minimumReceived = route.does_swap
+     ? (BigInt(route.estimated_amount_out) * BigInt(10000 - safeSlippage * 100)) / BigInt(10000)
+     : BigInt(route.estimated_amount_out)
+ } else {
  const minimumReceived = route.does_swap
-   ? (BigInt(route.estimated_amount_out) * BigInt(10000 - Number(values.slippagePercent) * 100)) /
+   ? (BigInt(route.estimated_amount_out) * BigInt(10000 - slippage * 100)) /
      BigInt(10000)
    : BigInt(route.estimated_amount_out)
+ }
🧹 Nitpick comments (1)
packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1)

70-76: Consider adding explicit dimensions to the asset logo.

Line 73 includes className={styles.detailToken} (improvement since the past review), but still lacks explicit width and height attributes. Adding them helps prevent layout shift and improves rendering consistency.

- <img src={dstAsset.logo_uri} alt={dstAsset.symbol} className={styles.detailToken} />
+ <img src={dstAsset.logo_uri} alt={dstAsset.symbol} className={styles.detailToken} width="16" height="16" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9efb6 and b84d56b.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css (1 hunks)
  • packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.module.css
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx (5)
packages/interwovenkit-react/src/pages/bridge/data/simulate.ts (1)
  • RouterRouteResponseJson (14-19)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)
  • useBridgePreviewState (53-55)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (1)
  • useAllSkipAssets (15-43)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInitiaAddress (16-20)
packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts (1)
  • useConnectedWalletIcon (3-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet

Comment on lines 60 to 67
{address && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
<img src={walletIcon} alt="Wallet" /> {truncate(address)}
</p>
</div>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against undefined walletIcon to prevent invalid HTML.

Line 64 renders <img src={walletIcon}> without checking if walletIcon is defined. When useConnectedWalletIcon() returns undefined, this produces <img src="undefined">, causing a failed network request and a broken image icon.

Conditionally render the wallet icon:

  {address && (
    <div className={styles.detail}>
      <p className={styles.detailLabel}>Receiving address</p>
      <p className={styles.detailValue}>
-       <img src={walletIcon} alt="Wallet" /> {truncate(address)}
+       {walletIcon && <img src={walletIcon} alt="Wallet" />} {truncate(address)}
      </p>
    </div>
  )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{address && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
<img src={walletIcon} alt="Wallet" /> {truncate(address)}
</p>
</div>
)}
{address && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
{walletIcon && <img src={walletIcon} alt="Wallet" />} {truncate(address)}
</p>
</div>
)}
🤖 Prompt for AI Agents
In packages/interwovenkit-react/src/pages/deposit/DepositTxDetails.tsx around
lines 60 to 67, the JSX renders <img src={walletIcon}> without guarding against
walletIcon being undefined which leads to src="undefined" and a broken request;
update the rendering so the <img> is only output when walletIcon is truthy
(e.g., conditionally render the <img> or render a placeholder/svg when
walletIcon is falsy), leaving the rest of the address markup unchanged.

Copy link

@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 @packages/interwovenkit-react/src/pages/deposit/Fields.module.css:
- Around line 163-174: The .detailsButton rule in Fields.module.css is missing
display: flex so its justify-content, align-items, and gap settings are ignored;
update the .detailsButton CSS block to include display: flex (e.g., add
"display: flex;") so the layout properties take effect and the button renders as
intended.
- Around line 96-106: The .input style lacks keyboard focus indicators; update
the CSS for the .input selector to include explicit focus styles (use
.input:focus and .input:focus-visible) that provide a high-contrast visible
outline or box-shadow (e.g., a 2px solid or accessible colored ring) and ensure
you do not rely on outline: none without replacement; apply the focus styles to
the .input class used in the form fields so keyboard users can see focus state
and the indicator meets contrast requirements.
- Around line 163-178: Add explicit keyboard focus styles for the .detailsButton
selector: define :focus and preferably :focus-visible rules for .detailsButton
to provide a visible indicator (e.g., outline or box-shadow and outline-offset)
and ensure the color/contrast meets accessibility; keep hover color behavior but
ensure focus state does not rely on :hover, and include focus styles for both
keyboard focus and when the element is focused programmatically.
- Around line 6-17: The .back CSS rule lacks keyboard focus styles; add explicit
focus indicators by extending the .back selector with .back:focus and/or
.back:focus-visible (and optionally .back:active) to apply a visible outline or
box-shadow and ensure sufficient color/contrast for accessibility; keep hover
behavior intact and prefer focus-visible to avoid styling mouse clicks while
providing a clear keyboard focus ring for the .back element.
- Around line 38-54: The .asset rule lacks keyboard focus styles; make the
interactive asset element visibly focusable by adding focus rules (e.g.,
.asset:focus and/or .asset:focus-visible) that apply a clear outline or
box-shadow and preserve the existing hover look, and ensure the element that
uses .asset is focusable (has tabindex or is a button/anchor) so keyboard users
see the indicator; update the CSS selectors for .asset:focus and
.asset:focus-visible to use a high-contrast outline/box-shadow and maintain
border-radius and padding for consistent appearance.
- Around line 126-140: The .maxButton lacks keyboard focus indicators; add
explicit focus styles for .maxButton (and optionally .maxButton:focus-visible)
to show a clear visible outline/box-shadow (contrasting color or using
var(--focus-color)), set outline-offset and border-radius to match the element,
and ensure the span styling remains readable on focus; if the element with class
.maxButton is not naturally focusable, make it focusable in markup (e.g., add
tabindex="0") so the new focus styles apply to keyboard users.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 67f9a25 and 46a5310.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/pages/deposit/Fields.module.css
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet

Comment on lines 6 to 17
.back {
position: absolute;
top: 16px;
left: 20px;

color: var(--gray-4);
padding: 3px;
}

.back:hover {
color: var(--gray-2);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .back button still lacks explicit keyboard focus indicators, which are essential for accessibility compliance and keyboard navigation.

🔎 Add focus styles
 .back:hover {
   color: var(--gray-2);
 }
+
+.back:focus-visible {
+  outline: 2px solid var(--gray-3);
+  outline-offset: 2px;
+}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 6 - 17, The .back CSS rule lacks keyboard focus styles; add explicit focus
indicators by extending the .back selector with .back:focus and/or
.back:focus-visible (and optionally .back:active) to apply a visible outline or
box-shadow and ensure sufficient color/contrast for accessibility; keep hover
behavior intact and prefer focus-visible to avoid styling mouse clicks while
providing a clear keyboard focus ring for the .back element.

Comment on lines +38 to +54
.asset {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;

width: 100%;
height: 56px;

background-color: var(--gray-7);
border-radius: 8px;
column-gap: 8px;
padding: 6px 12px;
}

.asset:hover {
background-color: var(--gray-6);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .asset selector still lacks explicit focus indicators. Since this is an interactive element for asset selection, keyboard users need visible focus states.

🔎 Add focus styles
 .asset:hover {
   background-color: var(--gray-6);
 }
+
+.asset:focus-visible {
+  outline: 2px solid var(--gray-3);
+  outline-offset: 2px;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.asset {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
width: 100%;
height: 56px;
background-color: var(--gray-7);
border-radius: 8px;
column-gap: 8px;
padding: 6px 12px;
}
.asset:hover {
background-color: var(--gray-6);
}
.asset {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
width: 100%;
height: 56px;
background-color: var(--gray-7);
border-radius: 8px;
column-gap: 8px;
padding: 6px 12px;
}
.asset:hover {
background-color: var(--gray-6);
}
.asset:focus-visible {
outline: 2px solid var(--gray-3);
outline-offset: 2px;
}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 38 - 54, The .asset rule lacks keyboard focus styles; make the interactive
asset element visibly focusable by adding focus rules (e.g., .asset:focus and/or
.asset:focus-visible) that apply a clear outline or box-shadow and preserve the
existing hover look, and ensure the element that uses .asset is focusable (has
tabindex or is a button/anchor) so keyboard users see the indicator; update the
CSS selectors for .asset:focus and .asset:focus-visible to use a high-contrast
outline/box-shadow and maintain border-radius and padding for consistent
appearance.

Comment on lines +96 to +106
.input {
width: 100%;
height: 56px;

background-color: var(--gray-7);
border-radius: 8px;
font-size: 16px;
font-weight: 500;
padding: 18px 16px;
text-align: left;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .input field still lacks explicit focus indicators, which are necessary for form accessibility and keyboard navigation.

🔎 Add focus styles
 .input {
   width: 100%;
   height: 56px;
 
   background-color: var(--gray-7);
   border-radius: 8px;
   font-size: 16px;
   font-weight: 500;
   padding: 18px 16px;
   text-align: left;
 }
+
+.input:focus {
+  outline: 2px solid var(--gray-3);
+  outline-offset: -2px;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.input {
width: 100%;
height: 56px;
background-color: var(--gray-7);
border-radius: 8px;
font-size: 16px;
font-weight: 500;
padding: 18px 16px;
text-align: left;
}
.input {
width: 100%;
height: 56px;
background-color: var(--gray-7);
border-radius: 8px;
font-size: 16px;
font-weight: 500;
padding: 18px 16px;
text-align: left;
}
.input:focus {
outline: 2px solid var(--gray-3);
outline-offset: -2px;
}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 96 - 106, The .input style lacks keyboard focus indicators; update the CSS
for the .input selector to include explicit focus styles (use .input:focus and
.input:focus-visible) that provide a high-contrast visible outline or box-shadow
(e.g., a 2px solid or accessible colored ring) and ensure you do not rely on
outline: none without replacement; apply the focus styles to the .input class
used in the form fields so keyboard users can see focus state and the indicator
meets contrast requirements.

Comment on lines +126 to +140
.maxButton {
display: flex;
gap: 4px;

color: var(--gray-3);
font-family: var(--monospace);
font-size: 12px;
font-weight: 500;
}

.maxButton span {
color: var(--gray-0);
font-family: var(--font-family);
font-weight: 600;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .maxButton still lacks explicit focus indicators for keyboard navigation.

🔎 Add focus styles
 .maxButton span {
   color: var(--gray-0);
   font-family: var(--font-family);
   font-weight: 600;
 }
+
+.maxButton:focus-visible {
+  outline: 2px solid var(--gray-3);
+  outline-offset: 2px;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.maxButton {
display: flex;
gap: 4px;
color: var(--gray-3);
font-family: var(--monospace);
font-size: 12px;
font-weight: 500;
}
.maxButton span {
color: var(--gray-0);
font-family: var(--font-family);
font-weight: 600;
}
.maxButton {
display: flex;
gap: 4px;
color: var(--gray-3);
font-family: var(--monospace);
font-size: 12px;
font-weight: 500;
}
.maxButton span {
color: var(--gray-0);
font-family: var(--font-family);
font-weight: 600;
}
.maxButton:focus-visible {
outline: 2px solid var(--gray-3);
outline-offset: 2px;
}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 126 - 140, The .maxButton lacks keyboard focus indicators; add explicit
focus styles for .maxButton (and optionally .maxButton:focus-visible) to show a
clear visible outline/box-shadow (contrasting color or using
var(--focus-color)), set outline-offset and border-radius to match the element,
and ensure the span styling remains readable on focus; if the element with class
.maxButton is not naturally focusable, make it focusable in markup (e.g., add
tabindex="0") so the new focus styles apply to keyboard users.

Comment on lines +163 to +174
.detailsButton {
justify-content: flex-start;
align-items: center;
gap: 2px;

width: fit-content;
height: 18px;

color: var(--gray-3);
font-size: 12px;
font-weight: 500;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

CRITICAL: Add missing display: flex; property.

The .detailsButton class uses justify-content, align-items, and gap properties (lines 164-166), which only work with flexbox or grid layout. Without display: flex;, these properties are ignored by the browser and the button will not layout correctly.

This issue was flagged in a previous review but has not been addressed.

🔎 Proposed fix
 .detailsButton {
+  display: flex;
   justify-content: flex-start;
   align-items: center;
   gap: 2px;
 
   width: fit-content;
   height: 18px;
 
   color: var(--gray-3);
   font-size: 12px;
   font-weight: 500;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.detailsButton {
justify-content: flex-start;
align-items: center;
gap: 2px;
width: fit-content;
height: 18px;
color: var(--gray-3);
font-size: 12px;
font-weight: 500;
}
.detailsButton {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 2px;
width: fit-content;
height: 18px;
color: var(--gray-3);
font-size: 12px;
font-weight: 500;
}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 163 - 174, The .detailsButton rule in Fields.module.css is missing
display: flex so its justify-content, align-items, and gap settings are ignored;
update the .detailsButton CSS block to include display: flex (e.g., add
"display: flex;") so the layout properties take effect and the button renders as
intended.

Comment on lines +163 to +178
.detailsButton {
justify-content: flex-start;
align-items: center;
gap: 2px;

width: fit-content;
height: 18px;

color: var(--gray-3);
font-size: 12px;
font-weight: 500;
}

.detailsButton:hover {
color: var(--gray-1);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add focus styles for keyboard accessibility.

The .detailsButton still lacks explicit focus indicators for keyboard navigation.

🔎 Add focus styles
 .detailsButton:hover {
   color: var(--gray-1);
 }
+
+.detailsButton:focus-visible {
+  outline: 2px solid var(--gray-3);
+  outline-offset: 2px;
+}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/Fields.module.css around
lines 163 - 178, Add explicit keyboard focus styles for the .detailsButton
selector: define :focus and preferably :focus-visible rules for .detailsButton
to provide a visible indicator (e.g., outline or box-shadow and outline-offset)
and ensure the color/contrast meets accessibility; keep hover color behavior but
ensure focus state does not rely on :hover, and include focus styles for both
keyboard focus and when the element is focused programmatically.

Copy link

@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: 0

🧹 Nitpick comments (1)
packages/interwovenkit-react/src/pages/deposit/Deposit.tsx (1)

44-55: Consider adding a default case for runtime safety.

The switch statement lacks a default case. While TypeScript provides compile-time exhaustiveness checking, adding a default case improves runtime safety and makes bugs like the "completed" vs "transfer-completed" mismatch more apparent during development.

🔎 Suggested addition
   const renderPage = (currentPage: typeof page) => {
     switch (currentPage) {
       case "select-local":
         return <SelectLocalAsset />
       case "select-external":
         return <SelectExternalAsset />
       case "fields":
         return <DepositFields />
-      case "completed":
+      case "transfer-completed":
         return <TransferCompleted type="deposit" />
+      default:
+        // Exhaustiveness check - should never reach here
+        console.error(`Unknown page: ${currentPage}`)
+        return null
     }
   }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 46a5310 and f5f81cf.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/deposit/Deposit.tsx
  • packages/interwovenkit-react/src/pages/deposit/Withdraw.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/interwovenkit-react/src/pages/deposit/Withdraw.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/Deposit.tsx (3)
packages/interwovenkit-react/src/pages/deposit/Withdraw.tsx (1)
  • FormValues (11-14)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (2)
  • useAllBalancesQuery (17-46)
  • useTransferForm (137-139)
packages/interwovenkit-react/src/pages/deposit/TransferCompleted.tsx (1)
  • TransferCompleted (34-169)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet

Copy link

@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: 0

🧹 Nitpick comments (2)
packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (1)

19-25: Consider separating error display from button label.

The current implementation displays error text inside the button label when the button is disabled. While this creates an inline validation pattern, it has some UX considerations:

  • Unconventional pattern: Most applications display errors separately (above/below the button) rather than replacing the button text
  • Text overflow: Long error messages might not fit well within button constraints
  • Accessibility: Screen readers may not clearly communicate that the button is disabled because of the error shown in the label

However, this pattern may be intentional for the modal context. If you prefer the current approach, consider:

  • Keeping error messages concise to fit button width
  • Testing with screen readers to ensure disabled state + error reason are announced properly
Alternative: Separate error display
  return (
    <Footer>
+     {error && <div className={styles.error}>{error}</div>}
      <Button.White
        onClick={() => mutate()}
        loading={isPending && "Signing transaction..."}
        disabled={!!error}
      >
-       {error || confirmMessage || "Confirm"}
+       {confirmMessage || "Confirm"}
      </Button.White>
    </Footer>
  )

This approach keeps button text stable and displays errors in a dedicated element.

packages/interwovenkit-react/src/pages/deposit/TransferFooter.tsx (1)

32-35: Extract duplicated onCompleted handler.

The onCompleted function is duplicated in both TransferFooterWithFee (lines 32-35) and TransferFooter (lines 184-187). Consider extracting this to a shared helper or defining it once at the top level of the file.

♻️ Refactor to extract shared logic
+const useTransferOnCompleted = () => {
+  const { setValue } = useTransferForm()
+  return (result: BridgeTxResult) => {
+    setValue("page", "completed")
+    setValue("result", result)
+  }
+}
+
 const TransferFooterWithFee = ({ tx, gas }: Props) => {
   const path = usePath()
   const isWithdraw = path.startsWith("/withdraw")
   const confirmMessage = isWithdraw ? "Withdraw" : "Deposit"
-  const { setValue } = useTransferForm()
-
-  function onCompleted(result: BridgeTxResult) {
-    setValue("page", "completed")
-    setValue("result", result)
-  }
+  const onCompleted = useTransferOnCompleted()

   // ... rest of the component
 }

 const TransferFooter = ({ tx, gas }: Props) => {
   const path = usePath()
-  const { setValue } = useTransferForm()
-
-  function onCompleted(result: BridgeTxResult) {
-    setValue("page", "completed")
-    setValue("result", result)
-  }
+  const onCompleted = useTransferOnCompleted()

   // ... rest of the component
 }

Also applies to: 184-187

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between f5f81cf and ee284e2.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx
  • packages/interwovenkit-react/src/pages/deposit/TransferFooter.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
packages/interwovenkit-react/src/pages/deposit/TransferFooter.tsx (9)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (1)
  • useTransferForm (137-139)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (2)
  • BridgeTxResult (57-66)
  • useBridgePreviewState (53-55)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (1)
  • useAllSkipAssets (15-43)
packages/interwovenkit-react/src/data/chains.ts (1)
  • useChain (97-100)
packages/interwovenkit-react/src/data/account.ts (1)
  • useBalances (43-47)
packages/interwovenkit-react/src/data/fee.ts (2)
  • useGasPrices (16-43)
  • useLastFeeDenom (45-75)
packages/interwovenkit-react/src/data/assets.ts (1)
  • useFindAsset (76-88)
packages/interwovenkit-react/src/public/data/constants.ts (1)
  • DEFAULT_GAS_ADJUSTMENT (3-3)
packages/interwovenkit-react/src/components/Dropdown.tsx (1)
  • DropdownOption (7-11)
packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (1)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (2)
  • BridgeTxResult (57-66)
  • useBridgeTx (73-297)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (3)
packages/interwovenkit-react/src/pages/bridge/BridgePreviewFooter.tsx (3)

1-1: LGTM! Type imports are correct.

The type imports for StdFee and BridgeTxResult properly support the new props and align with the bridge transaction handling in data/tx.ts.

Also applies to: 5-5


7-13: LGTM! Props interface extended appropriately.

The new optional props support the modal-driven flow requirements:

  • fee for custom fee override
  • onCompleted for handling transaction results
  • confirmMessage for custom button text
  • error for validation/error feedback

All props are optional, maintaining backward compatibility.


15-16: LGTM! Hook integration is correct.

The component correctly passes customFee and onCompleted to useBridgeTx, which according to the implementation in data/tx.ts supports these options for transaction customization and completion callbacks.

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx (1)

26-63: Race condition: stale fetch results can overwrite current data.

If the user changes inputs (slippage, amounts, etc.) while a fetch is in progress, multiple concurrent fetches will execute. The later fetch may complete first, then the earlier fetch overwrites it with stale transaction data. In a bridge/transaction context, this could cause the user to sign a transaction with incorrect parameters, potentially resulting in financial loss.

🔒 Proposed fix with cleanup and cancellation
 useEffect(() => {
+  let cancelled = false
+
   const fetchMessages = async () => {
     try {
       if (route.required_op_hook && !signedOpHook) {
         throw new Error("Op hook is required")
       }
 
       setLoading(true)
       setError(null)
 
       const params = {
         address_list: addressList,
         amount_in: route.amount_in,
         amount_out: route.amount_out,
         source_asset_chain_id: route.source_asset_chain_id,
         source_asset_denom: route.source_asset_denom,
         dest_asset_chain_id: route.dest_asset_chain_id,
         dest_asset_denom: route.dest_asset_denom,
         slippage_tolerance_percent: values.slippagePercent,
         operations: route.operations,
         signed_op_hook: signedOpHook,
       }
 
       const { txs } = await skip
         .post("v2/fungible/msgs", { json: params })
         .json<MsgsResponseJson>()
+
+      if (cancelled) return
+
       if (!txs || txs.length === 0) throw new Error("No transaction data found")
       const [tx] = txs
       setValue(tx)
     } catch (error) {
+      if (cancelled) return
       setError(error as Error)
     } finally {
+      if (!cancelled) {
         setLoading(false)
+      }
     }
   }
 
   fetchMessages()
+
+  return () => {
+    cancelled = true
+  }
 }, [route, values, addressList, signedOpHook, skip])
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between ee284e2 and 01d169a.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (1)
packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx (1)

69-77: LGTM! Cleaner loading state consolidation.

The refactor properly consolidates the loading and no-value conditions into a single guard clause, making the control flow more straightforward while maintaining type safety.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In @packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx:
- Around line 64-66: The empty-state text always says "deposit" but
SelectExternalAsset supports both flows; update the rendered message to branch
on the isWithdraw prop (used in this component) and show "withdraw" when
isWithdraw is true and "deposit" otherwise, still including localAsset.symbol
and using the same styles.empty element so the UI and styling remain unchanged.
- Around line 37-48: The back button rendered in SelectExternalAsset lacks an
accessible label for screen readers; update the <button> element (styles.close)
that contains <IconBack size={14} /> so it includes an accessible name—either
add an aria-label (e.g., aria-label="Back") or include visually-hidden text
describing the action (e.g., "Back to fields" or "Back to select local"
depending on context using isWithdraw/isExternalSelected) while keeping the
existing onClick logic that calls setValue("page", ...) intact.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 01d169a and 1948957.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.module.css
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.module.css
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx (2)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (4)
  • useExternalAssetOptions (86-122)
  • useTransferForm (137-139)
  • useLocalAssetDepositAsset (56-69)
  • useLocalAssetOptions (48-54)
packages/interwovenkit-react/src/data/config.ts (1)
  • useConfig (32-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet

Comment on lines 37 to 48
<button
className={styles.close}
onClick={() => {
if (isWithdraw || isExternalSelected) {
setValue("page", "fields")
} else {
setValue("page", "select-local")
}
}}
>
<IconBack size={14} />
</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add accessible label to back button.

The back button still lacks an accessible label for screen readers. Users relying on assistive technology won't know the button's purpose.

♿ Recommended fix
       <button
         className={styles.close}
+        aria-label={
+          isWithdraw || isExternalSelected
+            ? "Go back to transfer fields"
+            : "Go back to select local asset"
+        }
         onClick={() => {
           if (isWithdraw || isExternalSelected) {
             setValue("page", "fields")
           } else {
             setValue("page", "select-local")
           }
         }}
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className={styles.close}
onClick={() => {
if (isWithdraw || isExternalSelected) {
setValue("page", "fields")
} else {
setValue("page", "select-local")
}
}}
>
<IconBack size={14} />
</button>
<button
className={styles.close}
aria-label={
isWithdraw || isExternalSelected
? "Go back to transfer fields"
: "Go back to select local asset"
}
onClick={() => {
if (isWithdraw || isExternalSelected) {
setValue("page", "fields")
} else {
setValue("page", "select-local")
}
}}
>
<IconBack size={14} />
</button>
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx
around lines 37 - 48, The back button rendered in SelectExternalAsset lacks an
accessible label for screen readers; update the <button> element (styles.close)
that contains <IconBack size={14} /> so it includes an accessible name—either
add an aria-label (e.g., aria-label="Back") or include visually-hidden text
describing the action (e.g., "Back to fields" or "Back to select local"
depending on context using isWithdraw/isExternalSelected) while keeping the
existing onClick logic that calls setValue("page", ...) intact.

Comment on lines +64 to +66
<p className={styles.empty}>
You do not have supported assets to deposit {localAsset.symbol}.
</p>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Empty state message is incorrect for withdraw flow.

The message always references "deposit" but this component handles both deposit and withdraw flows (controlled by isWithdraw on line 25).

📝 Recommended fix
         <p className={styles.empty}>
-          You do not have supported assets to deposit {localAsset.symbol}.
+          {isWithdraw
+            ? `There are no available chains to withdraw ${localAsset.symbol} to.`
+            : `You do not have supported assets to deposit ${localAsset.symbol}.`}
         </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p className={styles.empty}>
You do not have supported assets to deposit {localAsset.symbol}.
</p>
<p className={styles.empty}>
{isWithdraw
? `There are no available chains to withdraw ${localAsset.symbol} to.`
: `You do not have supported assets to deposit ${localAsset.symbol}.`}
</p>
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx
around lines 64 - 66, The empty-state text always says "deposit" but
SelectExternalAsset supports both flows; update the rendered message to branch
on the isWithdraw prop (used in this component) and show "withdraw" when
isWithdraw is true and "deposit" otherwise, still including localAsset.symbol
and using the same styles.empty element so the UI and styling remain unchanged.

Comment on lines +99 to +134
<button
key={`${asset.chain_id}-${asset.denom}`}
className={clsx(styles.asset, isActive && styles.activeAsset)}
onClick={() => {
setValue(externalDenomKey, asset.denom)
setValue(externalChainIdKey, chain.chain_id)
if (!isWithdraw) setValue("quantity", "")
setValue("page", "fields")
}}
>
<div className={styles.iconContainer}>
<img src={asset.logo_uri} alt={asset.symbol} className={styles.assetIcon} />
<img
src={chain.logo_uri || ""}
alt={chain.chain_name}
className={styles.chainIcon}
/>
</div>
<p className={styles.assetName}>
{asset.symbol}{" "}
{isActive && (
<span>
<IconCheck size={16} />
</span>
)}
</p>
<p className={styles.assetChain}>on {chain.pretty_name}</p>
{balance && (
<>
<p className={styles.balance}>
{formatAmount(balance.amount, { decimals: balance.decimals || 6 })}
</p>
<p className={styles.value}>{formatValue(balance.value_usd || 0)}</p>
</>
)}
</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Asset buttons lack accessible labels.

Each asset button only contains visual elements (images, text nodes) without a programmatic label. Screen readers cannot convey the complete button purpose to users.

♿ Recommended fix

Add an aria-label to each button that describes the full action:

                 <button
                   key={`${asset.chain_id}-${asset.denom}`}
                   className={clsx(styles.asset, isActive && styles.activeAsset)}
+                  aria-label={`Select ${asset.symbol} on ${chain.pretty_name}${
+                    balance ? ` with balance ${formatAmount(balance.amount, { decimals: balance.decimals || 6 })}` : ""
+                  }`}
                   onClick={() => {
                     setValue(externalDenomKey, asset.denom)
                     setValue(externalChainIdKey, chain.chain_id)
                     if (!isWithdraw) setValue("quantity", "")
                     setValue("page", "fields")
                   }}
                 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
key={`${asset.chain_id}-${asset.denom}`}
className={clsx(styles.asset, isActive && styles.activeAsset)}
onClick={() => {
setValue(externalDenomKey, asset.denom)
setValue(externalChainIdKey, chain.chain_id)
if (!isWithdraw) setValue("quantity", "")
setValue("page", "fields")
}}
>
<div className={styles.iconContainer}>
<img src={asset.logo_uri} alt={asset.symbol} className={styles.assetIcon} />
<img
src={chain.logo_uri || ""}
alt={chain.chain_name}
className={styles.chainIcon}
/>
</div>
<p className={styles.assetName}>
{asset.symbol}{" "}
{isActive && (
<span>
<IconCheck size={16} />
</span>
)}
</p>
<p className={styles.assetChain}>on {chain.pretty_name}</p>
{balance && (
<>
<p className={styles.balance}>
{formatAmount(balance.amount, { decimals: balance.decimals || 6 })}
</p>
<p className={styles.value}>{formatValue(balance.value_usd || 0)}</p>
</>
)}
</button>
<button
key={`${asset.chain_id}-${asset.denom}`}
className={clsx(styles.asset, isActive && styles.activeAsset)}
aria-label={`Select ${asset.symbol} on ${chain.pretty_name}${
balance ? ` with balance ${formatAmount(balance.amount, { decimals: balance.decimals || 6 })}` : ""
}`}
onClick={() => {
setValue(externalDenomKey, asset.denom)
setValue(externalChainIdKey, chain.chain_id)
if (!isWithdraw) setValue("quantity", "")
setValue("page", "fields")
}}
>
<div className={styles.iconContainer}>
<img src={asset.logo_uri} alt={asset.symbol} className={styles.assetIcon} />
<img
src={chain.logo_uri || ""}
alt={chain.chain_name}
className={styles.chainIcon}
/>
</div>
<p className={styles.assetName}>
{asset.symbol}{" "}
{isActive && (
<span>
<IconCheck size={16} />
</span>
)}
</p>
<p className={styles.assetChain}>on {chain.pretty_name}</p>
{balance && (
<>
<p className={styles.balance}>
{formatAmount(balance.amount, { decimals: balance.decimals || 6 })}
</p>
<p className={styles.value}>{formatValue(balance.value_usd || 0)}</p>
</>
)}
</button>

Copy link

@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: 2

🤖 Fix all issues with AI agents
In @packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx:
- Around line 36-40: minimumReceived calculation can throw when
values.slippagePercent is missing or invalid; validate and normalize
slippagePercent before converting to BigInt: parse it to a finite number (e.g.,
use Number or parseFloat), fallback to 0 if NaN, clamp to allowed range (>=0),
compute slippageBasisPoints as Math.round(slippagePercent * 100) to get an
integer, then use that integer when computing BigInt(10000 -
slippageBasisPoints); update the expression around minimumReceived (the
route.does_swap branch) to use the validated slippageBasisPoints so BigInt never
receives NaN.
- Around line 66-72: The Receiving address block in TransferTxDetails uses
walletIcon from useConnectedWalletIcon() which can be undefined, causing a
broken <img>; update the rendering in the TransferTxDetails component so the
icon is only rendered when walletIcon is truthy (e.g., conditionally render the
<img> inside the detailValue or render a fallback element) and leave
truncate(address) unchanged; reference the walletIcon variable and the
useConnectedWalletIcon hook to locate the change.
🧹 Nitpick comments (2)
packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx (2)

15-17: Redundant | undefined in optional prop type.

The ? modifier already implies the property can be undefined.

Suggested simplification
 interface Props {
-  renderFee?: (() => React.ReactNode) | undefined
+  renderFee?: () => React.ReactNode
 }

85-91: Consider fallback for missing logo_uri.

If dstAsset.logo_uri is undefined or null, this renders a broken image. Consider providing a placeholder or conditionally rendering the image.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 781fa3d and 506a3bb.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx (5)
packages/interwovenkit-react/src/pages/bridge/data/simulate.ts (1)
  • RouterRouteResponseJson (14-19)
packages/interwovenkit-react/src/pages/bridge/data/tx.ts (1)
  • useBridgePreviewState (53-55)
packages/interwovenkit-react/src/pages/bridge/data/assets.ts (1)
  • useAllSkipAssets (15-43)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useInitiaAddress (17-21)
packages/interwovenkit-react/src/hooks/useConnectedWalletIcon.ts (1)
  • useConnectedWalletIcon (3-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (1)
packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx (1)

21-22: Potential runtime error if values is undefined.

useBridgePreviewState() returns useLocationState<BridgePreviewState>(), which may return undefined if the location state is not set. Destructuring values on line 21 and then accessing dstDenom, dstChainId on line 22 will throw if values is undefined.

Consider adding a guard or providing defaults:

Suggested defensive approach
- const { values } = useBridgePreviewState()
- const { dstDenom, dstChainId } = values
+ const state = useBridgePreviewState()
+ const values = state?.values
+ const { dstDenom, dstChainId } = values ?? {}

Then update the early return on line 34:

- if (!route || !dstAsset) return null
+ if (!route || !dstAsset || !values) return null

Comment on lines +36 to +40
const minimumReceived = route.does_swap
? (BigInt(route.amount_out || route.estimated_amount_out) *
BigInt(10000 - Number(values.slippagePercent) * 100)) /
BigInt(10000)
: BigInt(route.amount_out || route.estimated_amount_out)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential BigInt conversion error if slippagePercent is invalid.

If values.slippagePercent is undefined or not a valid number, Number(values.slippagePercent) * 100 yields NaN, and BigInt(10000 - NaN) throws a TypeError.

Suggested fix with fallback
  const minimumReceived = route.does_swap
-   ? (BigInt(route.amount_out || route.estimated_amount_out) *
-       BigInt(10000 - Number(values.slippagePercent) * 100)) /
-     BigInt(10000)
+   ? (BigInt(route.amount_out || route.estimated_amount_out) *
+       BigInt(10000 - Math.round((Number(values.slippagePercent) || 0) * 100))) /
+     BigInt(10000)
    : BigInt(route.amount_out || route.estimated_amount_out)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const minimumReceived = route.does_swap
? (BigInt(route.amount_out || route.estimated_amount_out) *
BigInt(10000 - Number(values.slippagePercent) * 100)) /
BigInt(10000)
: BigInt(route.amount_out || route.estimated_amount_out)
const minimumReceived = route.does_swap
? (BigInt(route.amount_out || route.estimated_amount_out) *
BigInt(10000 - Math.round((Number(values.slippagePercent) || 0) * 100))) /
BigInt(10000)
: BigInt(route.amount_out || route.estimated_amount_out)
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx around
lines 36 - 40, minimumReceived calculation can throw when values.slippagePercent
is missing or invalid; validate and normalize slippagePercent before converting
to BigInt: parse it to a finite number (e.g., use Number or parseFloat),
fallback to 0 if NaN, clamp to allowed range (>=0), compute slippageBasisPoints
as Math.round(slippagePercent * 100) to get an integer, then use that integer
when computing BigInt(10000 - slippageBasisPoints); update the expression around
minimumReceived (the route.does_swap branch) to use the validated
slippageBasisPoints so BigInt never receives NaN.

Comment on lines +66 to +72
{address && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
<img src={walletIcon} alt="Wallet" height={12} width={12} /> {truncate(address)}
</p>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle undefined walletIcon to avoid broken image.

useConnectedWalletIcon() can return undefined when no wallet connector is present. Rendering <img src={walletIcon} /> with an undefined src displays a broken image icon.

Suggested fix
- {address && (
+ {address && walletIcon && (
    <div className={styles.detail}>
      <p className={styles.detailLabel}>Receiving address</p>
      <p className={styles.detailValue}>
        <img src={walletIcon} alt="Wallet" height={12} width={12} /> {truncate(address)}
      </p>
    </div>
  )}

Alternatively, conditionally render the icon only if defined:

  <p className={styles.detailValue}>
-   <img src={walletIcon} alt="Wallet" height={12} width={12} /> {truncate(address)}
+   {walletIcon && <img src={walletIcon} alt="Wallet" height={12} width={12} />} {truncate(address)}
  </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{address && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
<img src={walletIcon} alt="Wallet" height={12} width={12} /> {truncate(address)}
</p>
</div>
{address && walletIcon && (
<div className={styles.detail}>
<p className={styles.detailLabel}>Receiving address</p>
<p className={styles.detailValue}>
<img src={walletIcon} alt="Wallet" height={12} width={12} /> {truncate(address)}
</p>
</div>
)}
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/TransferTxDetails.tsx around
lines 66 - 72, The Receiving address block in TransferTxDetails uses walletIcon
from useConnectedWalletIcon() which can be undefined, causing a broken <img>;
update the rendering in the TransferTxDetails component so the icon is only
rendered when walletIcon is truthy (e.g., conditionally render the <img> inside
the detailValue or render a fallback element) and leave truncate(address)
unchanged; reference the walletIcon variable and the useConnectedWalletIcon hook
to locate the change.

Copy link

@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: 4

🤖 Fix all issues with AI agents
In @packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx:
- Line 74: The recipient assignment uses
InitiaAddress(state.recipientAddress).hex which can throw on invalid input; wrap
the conversion in a validation/try-catch around InitiaAddress (referencing
InitiaAddress and state.recipientAddress) and on error fallback to hexAddress
(or set recipient to an empty/validated value) and optionally log or surface the
error so the component won't crash; ensure the resulting value assigned to
recipient is always a safe hex string.
- Around line 54-60: Refactor the imperative disabledMessage logic in
WithdrawFields.tsx into a memoized value: replace the let/if block that sets
disabledMessage (which checks localAsset, Number(quantity), and compares
Number(quantity) to Number(formatAmount(balance, { decimals:
localAsset?.decimals || 6 }))) with a useMemo hook that returns the same string
values ("Select asset", "Enter amount", "Insufficient balance" or undefined)
based on those same inputs; ensure the dependency array includes localAsset,
quantity, balance and any helper like formatAmount so the value updates
correctly and matches the pattern used in DepositFields.tsx.
- Around line 68-81: The effect depends on the unstable getValues reference
causing re-runs; wrap getValues in a stable callback (e.g., create
stableGetValues via useCallback(() => getValues(), [getValues])) and replace
getValues with stableGetValues in the useEffect dependency array (or
alternatively read form values once outside the effect and pass that snapshot
into navigate) so the effect no longer retriggers on each render; update
references to getValues in WithdrawFields.tsx accordingly.

In @packages/interwovenkit-react/src/public/app/Modal.tsx:
- Line 18: The Dialog.Backdrop currently calls closeModal via onClick which can
duplicate the dialog's built-in dismiss behavior; remove the explicit onClick
from Dialog.Backdrop and rely on Dialog.Root's dismissible/onOpenChange handling
(or alternatively set dismissible={false} on Dialog.Root if you want to handle
backdrop clicks manually), ensuring closeModal is only invoked once via the
onOpenChange handler that receives open state changes.
🧹 Nitpick comments (6)
packages/interwovenkit-react/src/public/app/Modal.tsx (1)

19-19: Unnecessary clsx wrapper for single class.

clsx(styles.modal) with a single argument adds no value. You can pass styles.modal directly.

Suggested simplification
-        <Dialog.Popup className={clsx(styles.modal)} ref={setContainer}>
+        <Dialog.Popup className={styles.modal} ref={setContainer}>
packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx (1)

117-125: Optimize balance comparison in MAX button.

The onClick handler compares Number(quantity) with formatAmount(balance, ...) on every click, and formatAmount is also called on line 124. Consider computing the formatted balance once outside the handler.

♻️ Compute formatted balance once
+ const formattedBalance = useMemo(
+   () => formatAmount(balance, { decimals: localAsset?.decimals || 6 }),
+   [balance, localAsset?.decimals]
+ )
+
  {balance && (
    <div className={styles.balanceContainer}>
      <p className={styles.value}>{quantityValue ? formatValue(quantityValue) : "$-"}</p>

      <button
        className={styles.maxButton}
        onClick={() => {
-         if (
-           Number(quantity) ===
-           Number(formatAmount(balance, { decimals: localAsset?.decimals || 6 }))
-         )
+         if (Number(quantity) === Number(formattedBalance))
            return

-         setValue("quantity", formatAmount(balance, { decimals: localAsset?.decimals || 6 }))
+         setValue("quantity", formattedBalance)
        }}
      >
-       <IconWallet size={16} />{" "}
-       {formatAmount(balance, { decimals: localAsset?.decimals || 6 })} <span>MAX</span>
+       <IconWallet size={16} /> {formattedBalance} <span>MAX</span>
      </button>
    </div>
  )}
packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (2)

62-63: Remove or complete the incomplete comment.

The comment "Destructure error fields in deps to properly track each field change" doesn't align with the actual dependencies [quantity, balance, externalAsset]. There are no "error fields" being destructured, making this comment misleading.

📝 Clarify or remove comment
-   // Destructure error fields in deps to properly track each field change
  }, [quantity, balance, externalAsset])

32-198: Consider extracting shared logic to reduce duplication.

DepositFields.tsx and WithdrawFields.tsx share substantial logic (balance handling, MAX button, route queries, footer rendering, navigation effects). This duplication increases maintenance burden and the risk of inconsistencies (e.g., the disabledMessage implementation differs between them).

Consider extracting shared logic into:

  1. A custom hook for balance/quantity/MAX logic
  2. A shared TransferFields component that accepts a mode prop ("deposit" or "withdraw")
  3. Shared utility functions for address conversion, balance formatting, etc.

This would ensure consistent behavior and reduce the risk of divergence.

packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx (2)

30-50: Optimize function definition to prevent recreation.

The renderBackButton function is defined inside the component body, causing it to be recreated on every render. While this doesn't break functionality, moving it outside the component or using useCallback would be more idiomatic.

♻️ Move function outside or use useCallback

Option 1: Move outside (preferred if no dependencies change)

+function BackButton({
+  isWithdraw,
+  isExternalSelected,
+  optionsLength,
+  setValue,
+}: {
+  isWithdraw: boolean
+  isExternalSelected: boolean
+  optionsLength: number
+  setValue: (key: string, value: string) => void
+}) {
+  if (!isExternalSelected && optionsLength <= 1 && !isWithdraw) return null
+
+  return (
+    <button
+      className={styles.close}
+      onClick={() => {
+        if (isWithdraw || isExternalSelected) {
+          setValue("page", "fields")
+        } else {
+          setValue("page", "select-local")
+        }
+      }}
+    >
+      <IconBack size={20} />
+    </button>
+  )
+}
+
 const SelectExternalAsset = () => {
   // ... other code ...

-  function renderBackButton() {
-    const externalDenom = isWithdraw ? dstDenom : srcDenom
-    const externalChainId = isWithdraw ? dstChainId : srcChainId
-    const isExternalSelected = externalDenom && externalChainId
-    if (!isExternalSelected && options.length <= 1 && !isWithdraw) return null
-
-    return (
-      <button
-        className={styles.close}
-        onClick={() => {
-          if (isWithdraw || isExternalSelected) {
-            setValue("page", "fields")
-          } else {
-            setValue("page", "select-local")
-          }
-        }}
-      >
-        <IconBack size={20} />
-      </button>
-    )
-  }
+  const externalDenom = isWithdraw ? dstDenom : srcDenom
+  const externalChainId = isWithdraw ? dstChainId : srcChainId
+  const isExternalSelected = !!(externalDenom && externalChainId)

   // ... rest of component ...
-  {renderBackButton()}
+  <BackButton
+    isWithdraw={isWithdraw}
+    isExternalSelected={isExternalSelected}
+    optionsLength={options.length}
+    setValue={setValue}
+  />

88-136: Memoize sorted asset list to avoid re-sorting on every render.

The asset sorting by value_usd (lines 90-93) executes on every render, even when filteredAssets hasn't changed. This is unnecessary work, especially with large asset lists.

Additionally, lines 95-96 recompute externalDenom and externalChainId for each asset in the map, when these values are constant for the entire list.

⚡ Memoize sorted assets and lift constant variables
+ const externalDenom = isWithdraw ? dstDenom : srcDenom
+ const externalChainId = isWithdraw ? dstChainId : srcChainId
+
+ const sortedAssets = useMemo(
+   () =>
+     filteredAssets.sort(
+       ({ balance: a }, { balance: b }) =>
+         Number(b?.value_usd ?? 0) - Number(a?.value_usd ?? 0),
+     ),
+   [filteredAssets]
+ )

  <div className={styles.list}>
    {isLoading &&
      Array.from({ length: 5 }).map((_, i) => (
        <div key={i} className={clsx(styles.asset, styles.placeholder)}>
          <div className={styles.iconContainer}>
            <div className={styles.assetIcon} />
            <div className={styles.chainIcon} />
          </div>
          <div className={styles.assetPlaceholder} />
        </div>
      ))}
    {!isLoading &&
-     filteredAssets
-       .sort(
-         ({ balance: a }, { balance: b }) =>
-           Number(b?.value_usd ?? 0) - Number(a?.value_usd ?? 0),
-       )
-       .map(({ asset, chain, balance }) => {
-         const externalDenom = isWithdraw ? dstDenom : srcDenom
-         const externalChainId = isWithdraw ? dstChainId : srcChainId
+     sortedAssets.map(({ asset, chain, balance }) => {
          const isActive = externalDenom === asset.denom && externalChainId === chain.chain_id
          return (
            <button
              key={`${asset.chain_id}-${asset.denom}`}
              className={clsx(styles.asset, isActive && styles.activeAsset)}
              onClick={() => {
                setValue(externalDenomKey, asset.denom)
                setValue(externalChainIdKey, chain.chain_id)
                if (!isWithdraw) setValue("quantity", "")
                setValue("page", "fields")
              }}
            >
              {/* ... rest of button content ... */}
            </button>
          )
        })}
  </div>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 506a3bb and 3498695.

📒 Files selected for processing (7)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx
  • packages/interwovenkit-react/src/pages/deposit/Fields.module.css
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.module.css
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.tsx
  • packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx
  • packages/interwovenkit-react/src/public/app/Modal.module.css
  • packages/interwovenkit-react/src/public/app/Modal.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/interwovenkit-react/src/pages/deposit/Fields.module.css
  • packages/interwovenkit-react/src/public/app/Modal.module.css
  • packages/interwovenkit-react/src/pages/deposit/SelectExternalAsset.module.css
🧰 Additional context used
🧬 Code graph analysis (2)
packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx (4)
packages/interwovenkit-react/src/pages/bridge/data/simulate.ts (2)
  • RouterRouteResponseJson (14-19)
  • useRouteQuery (21-74)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (5)
  • useExternalAssetOptions (86-122)
  • useAllBalancesQuery (17-46)
  • useTransferForm (137-139)
  • useLocalAssetDepositAsset (56-69)
  • useExternalDepositAsset (71-84)
packages/interwovenkit-react/src/pages/bridge/data/chains.ts (1)
  • useFindSkipChain (56-78)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useHexAddress (23-29)
packages/interwovenkit-react/src/public/app/Modal.tsx (1)
packages/interwovenkit-react/src/data/ui.ts (1)
  • useModal (35-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (1)
packages/interwovenkit-react/src/public/app/Modal.tsx (1)

10-27: Overall component structure looks good.

The Modal correctly:

  • Uses controlled Dialog state via useModal hook
  • Leverages portal container for proper DOM mounting
  • Provides accessible close button with aria-label
  • Forwards ref to setContainer for nested portal context

Comment on lines +68 to +81
useEffect(() => {
navigate(0, {
...state,
route,
values: {
sender: hexAddress,
recipient: state.recipientAddress ? InitiaAddress(state.recipientAddress).hex : hexAddress,
slippagePercent: "1",
...getValues(),
},
})

// we don't want to trigger this when state changes, to avoid infinite loop
}, [route, getValues, hexAddress, navigate]) // eslint-disable-line react-hooks/exhaustive-deps
Copy link

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
# First, let's look at the WithdrawFields.tsx file to understand context
cd packages/interwovenkit-react/src/pages/deposit/ 2>/dev/null && ls -la | head -20

Repository: initia-labs/interwovenkit

Length of output: 1374


🏁 Script executed:

# Get the complete WithdrawFields.tsx file to understand the full context
wc -l packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx

Repository: initia-labs/interwovenkit

Length of output: 138


🏁 Script executed:

# Read the WithdrawFields.tsx file around the mentioned lines
head -100 packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx | tail -50

Repository: initia-labs/interwovenkit

Length of output: 1577


🏁 Script executed:

# Also look at the imports to understand where getValues comes from
head -20 packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx

Repository: initia-labs/interwovenkit

Length of output: 1040


🏁 Script executed:

# Search for other useEffect hooks that depend on getValues in the deposit folder
rg -n -B 3 -A 10 'useEffect\(' packages/interwovenkit-react/src/pages/deposit/ | head -100

Repository: initia-labs/interwovenkit

Length of output: 2743


🏁 Script executed:

# Search for getValues and useForm usage in WithdrawFields.tsx
rg -n 'getValues|useForm' packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx

Repository: initia-labs/interwovenkit

Length of output: 258


🏁 Script executed:

# Look at the full WithdrawFields.tsx file to see the component structure
cat -n packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx | head -120

Repository: initia-labs/interwovenkit

Length of output: 5100


🏁 Script executed:

# Check where navigate comes from
rg -n 'useNavigate|navigate' packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx | head -20

Repository: initia-labs/interwovenkit

Length of output: 328


🏁 Script executed:

# Find the useTransferForm hook implementation
fd -n "hooks" packages/interwovenkit-react/src/pages/deposit/ -type f

Repository: initia-labs/interwovenkit

Length of output: 299


🏁 Script executed:

# Read the hooks file
cat packages/interwovenkit-react/src/pages/deposit/hooks.ts

Repository: initia-labs/interwovenkit

Length of output: 4841


🏁 Script executed:

# Find the useNavigate hook implementation
fd -n "router" packages/interwovenkit-react/src/lib/ -type f

Repository: initia-labs/interwovenkit

Length of output: 299


🏁 Script executed:

# Read the router file to understand useNavigate
cat packages/interwovenkit-react/src/lib/router.ts 2>/dev/null || cat packages/interwovenkit-react/src/lib/router.tsx 2>/dev/null || cat packages/interwovenkit-react/src/lib/router/index.ts 2>/dev/null

Repository: initia-labs/interwovenkit

Length of output: 198


🏁 Script executed:

# Find all files in the router directory
find packages/interwovenkit-react/src/lib/router* -type f

Repository: initia-labs/interwovenkit

Length of output: 296


🏁 Script executed:

# Look for the RouterContext file
cat packages/interwovenkit-react/src/lib/router/RouterContext.ts 2>/dev/null || cat packages/interwovenkit-react/src/lib/router/RouterContext.tsx 2>/dev/null

Repository: initia-labs/interwovenkit

Length of output: 1083


🏁 Script executed:

# Search for useNavigate implementation
rg -n 'export.*useNavigate|function useNavigate' packages/interwovenkit-react/src/lib/ -A 10

Repository: initia-labs/interwovenkit

Length of output: 824


🏁 Script executed:

# Check if getValues is wrapped in useCallback or similar in hooks.ts
rg -n 'useCallback|useMemo' packages/interwovenkit-react/src/pages/deposit/hooks.ts

Repository: initia-labs/interwovenkit

Length of output: 51


🏁 Script executed:

# Search the entire deposit folder for any memoization of getValues
rg -n 'useCallback.*getValues|useMemo.*getValues' packages/interwovenkit-react/src/pages/deposit/

Repository: initia-labs/interwovenkit

Length of output: 51


🏁 Script executed:

# Check the MemoryRouter implementation to see if navigate is memoized
cat packages/interwovenkit-react/src/lib/router/MemoryRouter.tsx

Repository: initia-labs/interwovenkit

Length of output: 1944


🏁 Script executed:

# Also verify react-hook-form's getValues behavior - check if there are any docs or comments
rg -n 'getValues|react-hook-form' packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx -B 2 -A 2

Repository: initia-labs/interwovenkit

Length of output: 660


🏁 Script executed:

# Check if there are any similar patterns in DepositFields.tsx
cat -n packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx | sed -n '71,84p'

Repository: initia-labs/interwovenkit

Length of output: 626


🏁 Script executed:

# Check useHexAddress implementation to see if it returns a stable value
rg -n 'export.*useHexAddress|function useHexAddress' packages/interwovenkit-react/ -A 10

Repository: initia-labs/interwovenkit

Length of output: 1078


🏁 Script executed:

# Check useRouteQuery to see if route changes frequently
rg -n 'export.*useRouteQuery|function useRouteQuery' packages/interwovenkit-react/ -B 5 -A 15 | head -60

Repository: initia-labs/interwovenkit

Length of output: 2115


🏁 Script executed:

# Let's understand what react-hook-form's getValues behavior is
# by checking the package.json to see the version
cat packages/interwovenkit-react/package.json | grep -A 2 -B 2 'react-hook-form'

Repository: initia-labs/interwovenkit

Length of output: 430


🏁 Script executed:

# Check if there's any documentation comment or usage of getValues elsewhere
rg -n 'getValues' packages/interwovenkit-react/src/pages/ --no-filename | head -30

Repository: initia-labs/interwovenkit

Length of output: 688


🏁 Script executed:

# Let's see the complete DepositFields.tsx to verify it has the same pattern
wc -l packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx

Repository: initia-labs/interwovenkit

Length of output: 137


🏁 Script executed:

# Check if there are any actual infinite loop issues reported or handled
rg -n 'infinite|loop' packages/interwovenkit-react/src/pages/deposit/ -i

Repository: initia-labs/interwovenkit

Length of output: 571


🌐 Web query:

react-hook-form v7 getValues stable reference memoized

💡 Result:

Short answer: React Hook Form's docs describe getValues as an optimized helper that reads values without subscribing or triggering re-renders, but they don't guarantee its function identity is stable across renders. If you need a stable/memoized reference for deps or callbacks, store/memoize it yourself.

How to make a stable reference

  • useRef: const getValuesRef = useRef(getValues); useEffect(() => { getValuesRef.current = getValues }, [getValues]); then call getValuesRef.current().
  • useCallback wrapper: const stableGetValues = useCallback((...args) => getValues(...args), [getValues]); (works if you accept re-creating stableGetValues only when getValues changes).
  • Or capture values via useWatch/useWatch + getValues combo when you need reactive values without rerenders.

References

  • getValues docs (description and behavior). [1]
  • FAQ: difference between watch and getValues. [2]
  • GitHub discussions about stable form state / object references (context why some form APIs may change references). [3]

References:
[1] https://react-hook-form.com/docs/useform/getvalues
[2] https://react-hook-form.com/faqs
[3] react-hook-form/react-hook-form#3706


Fix unstable getValues dependency in useEffect to prevent infinite loops.

The getValues function from react-hook-form v7 does not have a stable reference across renders—it returns a new function on each render. Including it in the effect dependency array causes the effect to re-run whenever it's called, which triggers navigation updates, re-renders the component, and generates a new getValues reference, creating an infinite loop cycle.

The current eslint-disable comment acknowledges excluding state to avoid infinite loops, but the real issue is getValues itself. Wrap getValues with useCallback to create a stable reference:

const stableGetValues = useCallback(() => getValues(), [getValues])

Then update the dependency array to use stableGetValues instead of getValues, or restructure the effect to capture form values directly without relying on getValues in the dependencies.

🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx around
lines 68 - 81, The effect depends on the unstable getValues reference causing
re-runs; wrap getValues in a stable callback (e.g., create stableGetValues via
useCallback(() => getValues(), [getValues])) and replace getValues with
stableGetValues in the useEffect dependency array (or alternatively read form
values once outside the effect and pass that snapshot into navigate) so the
effect no longer retriggers on each render; update references to getValues in
WithdrawFields.tsx accordingly.

route,
values: {
sender: hexAddress,
recipient: state.recipientAddress ? InitiaAddress(state.recipientAddress).hex : hexAddress,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for address conversion.

InitiaAddress(state.recipientAddress).hex can throw if state.recipientAddress contains an invalid address format. This would crash the component.

🛡️ Add try-catch or validation
  useEffect(() => {
+   let recipientHex = hexAddress
+   if (state.recipientAddress) {
+     try {
+       recipientHex = InitiaAddress(state.recipientAddress).hex
+     } catch {
+       // Fall back to hexAddress if conversion fails
+       recipientHex = hexAddress
+     }
+   }
+
    navigate(0, {
      ...state,
      route,
      values: {
        sender: hexAddress,
-       recipient: state.recipientAddress ? InitiaAddress(state.recipientAddress).hex : hexAddress,
+       recipient: recipientHex,
        slippagePercent: "1",
        ...getValues(),
      },
    })

    // we don't want to trigger this when state changes, to avoid infinite loop
  }, [route, getValues, hexAddress, navigate]) // eslint-disable-line react-hooks/exhaustive-deps
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
recipient: state.recipientAddress ? InitiaAddress(state.recipientAddress).hex : hexAddress,
useEffect(() => {
let recipientHex = hexAddress
if (state.recipientAddress) {
try {
recipientHex = InitiaAddress(state.recipientAddress).hex
} catch {
// Fall back to hexAddress if conversion fails
recipientHex = hexAddress
}
}
navigate(0, {
...state,
route,
values: {
sender: hexAddress,
recipient: recipientHex,
slippagePercent: "1",
...getValues(),
},
})
// we don't want to trigger this when state changes, to avoid infinite loop
}, [route, getValues, hexAddress, navigate]) // eslint-disable-line react-hooks/exhaustive-deps
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx at line
74, The recipient assignment uses InitiaAddress(state.recipientAddress).hex
which can throw on invalid input; wrap the conversion in a validation/try-catch
around InitiaAddress (referencing InitiaAddress and state.recipientAddress) and
on error fallback to hexAddress (or set recipient to an empty/validated value)
and optionally log or surface the error so the component won't crash; ensure the
resulting value assigned to recipient is always a safe hex string.

return (
<Dialog.Root open={isModalOpen} onOpenChange={(isOpen) => !isOpen && closeModal()}>
<Dialog.Portal container={portalContainer}>
<Dialog.Backdrop className={styles.backdrop} onClick={closeModal} />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential double-close with backdrop click handler.

Base UI Dialog has a dismissible prop (true by default) that automatically closes the dialog when clicking outside the Popup. Adding an explicit onClick={closeModal} on the Backdrop may cause closeModal() to be invoked twice—once from your handler and once from the dialog's internal dismiss logic via onOpenChange. This could trigger duplicate analytics events ("Widget Closed" tracked twice).

Consider removing the explicit onClick and relying on the built-in dismiss behavior, or explicitly set dismissible={false} on Dialog.Root if you want manual control.

Proposed fix (remove redundant onClick)
-        <Dialog.Backdrop className={styles.backdrop} onClick={closeModal} />
+        <Dialog.Backdrop className={styles.backdrop} />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Dialog.Backdrop className={styles.backdrop} onClick={closeModal} />
<Dialog.Backdrop className={styles.backdrop} />
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/public/app/Modal.tsx at line 18, The
Dialog.Backdrop currently calls closeModal via onClick which can duplicate the
dialog's built-in dismiss behavior; remove the explicit onClick from
Dialog.Backdrop and rely on Dialog.Root's dismissible/onOpenChange handling (or
alternatively set dismissible={false} on Dialog.Root if you want to handle
backdrop clicks manually), ensuring closeModal is only invoked once via the
onOpenChange handler that receives open state changes.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In @packages/interwovenkit-react/src/components/form/NumericInput.tsx:
- Line 30: The local displayValue state (const [displayValue, setDisplayValue])
causes two issues: empty string is treated as falsy by the expression using ||
and external updates to field.value are ignored; to fix, change any fallback
from displayValue || field.value to use nullish coalescing (displayValue ??
field.value) so "" is preserved, and add a useEffect that watches field.value
(or the form value) to call setDisplayValue when field.value changes so the
input stays in sync with programmatic updates; update any related logic that
compares Number(displayValue) so it handles "" explicitly if needed.
- Around line 42-48: The numeric equivalence check in NumericInput (variables
currentNumeric/newNumeric) treats empty string as "0", which blocks clearing "0"
to empty; update the condition so you still call field.onChange(sanitized) when
sanitized === "" (or when field.value === "" differs), and only perform the
Number(...) equality shortcut when both field.value and sanitized are non-empty;
locate the comparison around the if (Number(currentNumeric) !==
Number(newNumeric)) and adjust it to allow cleared values while keeping
numeric-equality suppression for genuinely equivalent numeric inputs before
calling field.onChange(sanitized).
- Around line 62-63: The paste validation regex in NumericInput.tsx
(formattedNumberRegex) rejects inputs like ".5"; update the check so it allows a
leading decimal by replacing the current regex with one that permits zero digits
before the decimal and requires digits after it (for example
/^[0-9,]*\.?[0-9]+$/), or alternatively call the existing sanitizeNumericInput
logic against pastedText instead of using formattedNumberRegex; ensure the same
symbols are updated (formattedNumberRegex and the if
(!formattedNumberRegex.test(pastedText)) return) so ".5" is accepted.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 566ac43 and ea52300.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/components/form/NumericInput.tsx
🔇 Additional comments (1)
packages/interwovenkit-react/src/components/form/NumericInput.tsx (1)

68-71: Props spread order allows overriding defaults.

The {...attrs} spread after placeholder and inputMode allows callers to override these defaults. This is likely intentional for flexibility, but verify this is the desired behavior.

function NumericInput<T extends FieldValues>(props: Props<T>) {
const { name, control, dp = 6, className, rules, ...attrs } = props
const autoFocusRef = useAutoFocus()
const [displayValue, setDisplayValue] = useState("")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

State desync risk with external form updates and falsy value handling.

Two related issues with the local displayValue state approach:

  1. Falsy empty string: displayValue || field.value uses the || operator, which falls back to field.value when displayValue is "" (empty string is falsy). Combined with the Number comparison treating "" as "0", users cannot clear an input that contains "0" — the input will keep showing "0".

  2. External updates ignored: If field.value is set programmatically (via form.setValue(), form.reset(), etc.), displayValue won't update, causing the input to show stale data.

🔧 Suggested fix using nullish coalescing and sync effect
 function NumericInput<T extends FieldValues>(props: Props<T>) {
   const { name, control, dp = 6, className, rules, ...attrs } = props
   const autoFocusRef = useAutoFocus()
-  const [displayValue, setDisplayValue] = useState("")
+  const [displayValue, setDisplayValue] = useState<string | undefined>(undefined)
 
   return (
     <Controller
       name={name}
       control={control}
       rules={rules}
       render={({ field }) => {
+        // Sync displayValue when field.value changes externally
+        const fieldValue = field.value ?? ""
+        if (displayValue !== undefined && displayValue !== fieldValue && Number(displayValue) === Number(fieldValue)) {
+          // Values are numerically equal but string differs - keep displayValue
+        } else if (displayValue === undefined || (Number(displayValue || "0") !== Number(fieldValue || "0") && displayValue !== fieldValue)) {
+          setDisplayValue(undefined)
+        }
+
         const handleChange = (newValue: string) => {
           const sanitized = sanitizeNumericInput(newValue, dp)
           setDisplayValue(sanitized)
           // ...
         }
 
         return (
           <input
             {...field}
-            value={displayValue || field.value}
+            value={displayValue ?? field.value}

Using ?? (nullish coalescing) instead of || ensures empty string "" is respected. Consider a more robust sync strategy for external updates.

Also applies to: 54-54

🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/components/form/NumericInput.tsx at line
30, The local displayValue state (const [displayValue, setDisplayValue]) causes
two issues: empty string is treated as falsy by the expression using || and
external updates to field.value are ignored; to fix, change any fallback from
displayValue || field.value to use nullish coalescing (displayValue ??
field.value) so "" is preserved, and add a useEffect that watches field.value
(or the form value) to call setDisplayValue when field.value changes so the
input stays in sync with programmatic updates; update any related logic that
compares Number(displayValue) so it handles "" explicitly if needed.

Comment on lines 42 to 48
// Only update form if numeric value changed
const currentNumeric = field.value === "" ? "0" : field.value
const newNumeric = sanitized === "" ? "0" : sanitized

if (Number(currentNumeric) !== Number(newNumeric)) {
field.onChange(sanitized)
}}
placeholder="0"
inputMode="decimal"
autoComplete="off"
{...attrs}
ref={mergeRefs([field.ref, autoFocusRef])}
/>
)}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Numeric equivalence check prevents clearing to empty when value is "0".

The logic treats empty string as "0" for comparison purposes:

const currentNumeric = field.value === "" ? "0" : field.value
const newNumeric = sanitized === "" ? "0" : sanitized

If field.value is "0" and the user clears the input, Number("0") === Number("0") is true, so field.onChange is never called. Combined with the || fallback on line 54, the input will refuse to clear.

🔧 Suggested fix to distinguish empty from zero
         const handleChange = (newValue: string) => {
           const sanitized = sanitizeNumericInput(newValue, dp)
           setDisplayValue(sanitized)
 
-          // Only update form if numeric value changed
-          const currentNumeric = field.value === "" ? "0" : field.value
-          const newNumeric = sanitized === "" ? "0" : sanitized
-
-          if (Number(currentNumeric) !== Number(newNumeric)) {
+          // Update form if string value changed (preserves "" vs "0" distinction)
+          if (field.value !== sanitized) {
             field.onChange(sanitized)
           }
         }

If the intent is to prevent unnecessary updates for numerically equivalent values (e.g., "1" vs "1.0"), consider a more nuanced check that still allows clearing.

🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/components/form/NumericInput.tsx around
lines 42 - 48, The numeric equivalence check in NumericInput (variables
currentNumeric/newNumeric) treats empty string as "0", which blocks clearing "0"
to empty; update the condition so you still call field.onChange(sanitized) when
sanitized === "" (or when field.value === "" differs), and only perform the
Number(...) equality shortcut when both field.value and sanitized are non-empty;
locate the comparison around the if (Number(currentNumeric) !==
Number(newNumeric)) and adjust it to allow cleared values while keeping
numeric-equality suppression for genuinely equivalent numeric inputs before
calling field.onChange(sanitized).

Comment on lines +62 to +63
const formattedNumberRegex = /^[0-9,]+(\.[0-9]+)?$/
if (!formattedNumberRegex.test(pastedText)) return
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Paste regex rejects valid decimal inputs like ".5".

The regex /^[0-9,]+(\.[0-9]+)?$/ requires at least one digit or comma before an optional decimal portion. This means pasting ".5" (a valid number that the normal input path accepts via sanitizeNumericInput) will be rejected.

🔧 Suggested fix to allow leading decimal
-              const formattedNumberRegex = /^[0-9,]+(\.[0-9]+)?$/
+              const formattedNumberRegex = /^[0-9,]*\.?[0-9]+$/

Or match the behavior more closely with what sanitizeNumericInput accepts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const formattedNumberRegex = /^[0-9,]+(\.[0-9]+)?$/
if (!formattedNumberRegex.test(pastedText)) return
const formattedNumberRegex = /^[0-9,]*\.?[0-9]+$/
if (!formattedNumberRegex.test(pastedText)) return
🤖 Prompt for AI Agents
In @packages/interwovenkit-react/src/components/form/NumericInput.tsx around
lines 62 - 63, The paste validation regex in NumericInput.tsx
(formattedNumberRegex) rejects inputs like ".5"; update the check so it allows a
leading decimal by replacing the current regex with one that permits zero digits
before the decimal and requires digits after it (for example
/^[0-9,]*\.?[0-9]+$/), or alternatively call the existing sanitizeNumericInput
logic against pastedText instead of using formattedNumberRegex; ensure the same
symbols are updated (formattedNumberRegex and the if
(!formattedNumberRegex.test(pastedText)) return) so ".5" is accepted.

Copy link

@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: 0

🧹 Nitpick comments (2)
packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (1)

54-63: Stale comment in dependency array.

The comment on line 62 mentions "Destructure error fields in deps to properly track each field change" but there are no error fields being destructured or used. This appears to be a leftover from copy-paste or incomplete refactoring.

Suggested fix
   const disabledMessage = useMemo(() => {
     if (!externalAsset) return "Select asset"
     if (!Number(quantity)) return "Enter amount"
     if (
       Number(quantity) > Number(formatAmount(balance, { decimals: externalAsset?.decimals || 6 }))
     )
       return "Insufficient balance"
-
-    // Destructure error fields in deps to properly track each field change
   }, [quantity, balance, externalAsset])
packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx (1)

139-164: Extract findChain(dstChainId) to a variable to avoid repeated lookups.

findChain(dstChainId) is called 3 times during render (lines 144, 145, 160). In DepositFields.tsx, this is correctly extracted to srcChain. Apply the same pattern here for consistency and minor performance improvement.

Suggested fix

Add near line 46:

  const localAsset = useLocalAssetDepositAsset()
  const externalAsset = useExternalDepositAsset()
+ const dstChain = dstChainId ? findChain(dstChainId) : null

Then update the JSX:

-             src={findChain(dstChainId)?.logo_uri || ""}
-             alt={findChain(dstChainId)?.pretty_name}
+             src={dstChain?.logo_uri || ""}
+             alt={dstChain?.pretty_name}
...
-           <span>on {findChain(dstChainId)?.pretty_name}</span>
+           <span>on {dstChain?.pretty_name}</span>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 36fdf63 and e9df165.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx
  • packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (4)
packages/interwovenkit-react/src/pages/bridge/data/simulate.ts (2)
  • RouterRouteResponseJson (14-19)
  • useRouteQuery (21-74)
packages/interwovenkit-react/src/pages/deposit/hooks.ts (4)
  • useAllBalancesQuery (17-46)
  • useTransferForm (137-139)
  • useLocalAssetDepositAsset (56-69)
  • useExternalDepositAsset (71-84)
packages/interwovenkit-react/src/pages/bridge/data/chains.ts (1)
  • useFindSkipChain (56-78)
packages/interwovenkit-react/src/public/data/hooks.ts (1)
  • useHexAddress (23-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (2)
packages/interwovenkit-react/src/pages/deposit/DepositFields.tsx (1)

32-198: LGTM!

The component correctly orchestrates the deposit flow with proper hooks integration, debounced quantity for route queries, and appropriate UI state handling. The render props pattern for the footer aligns with existing bridge patterns.

packages/interwovenkit-react/src/pages/deposit/WithdrawFields.tsx (1)

32-197: LGTM!

The withdraw flow component mirrors the deposit flow appropriately, with correct adjustments for source/destination asset handling. The form validation and route query integration work correctly.

Copy link

@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: 0

🧹 Nitpick comments (2)
packages/interwovenkit-react/src/components/form/NumericInput.tsx (2)

38-45: Consider handling intermediate invalid states like a lone decimal point.

The comparison Number(field.value || 0) !== Number(sanitized || 0) will always evaluate to true when sanitized is "." because Number(".") returns NaN, and NaN !== x is always true. This causes field.onChange to fire with "." as the value.

This may trigger validation errors during typing. If you want to suppress onChange for incomplete numeric inputs, consider adding a guard:

Suggested approach
         const handleChange = (newValue: string) => {
           const sanitized = sanitizeNumericInput(newValue, dp)
           setDisplayValue(sanitized)

+          // Skip onChange for incomplete inputs that aren't valid numbers
+          if (sanitized === "." || sanitized === "") {
+            if (sanitized === "" && field.value) field.onChange("")
+            return
+          }
+
           if (Number(field.value || 0) !== Number(sanitized || 0)) {
             field.onChange(sanitized)
           }
         }

58-68: Paste validation rejects valid decimal inputs like .5.

The regex /^[0-9,]+(\.[0-9]+)?$/ requires at least one digit or comma before the decimal point. Pasting .5 or .123 will be silently rejected, though these are valid numeric inputs that sanitizeNumericInput would otherwise handle correctly.

If this is intentional to enforce formatted numbers only, consider adding a comment. Otherwise:

Suggested fix to allow leading decimals
-              const formattedNumberRegex = /^[0-9,]+(\.[0-9]+)?$/
+              // Allow formatted numbers (1,234.56) and leading decimals (.5)
+              const formattedNumberRegex = /^[0-9,]*\.?[0-9]*$/
               if (!formattedNumberRegex.test(pastedText)) return
+              if (pastedText === "" || pastedText === ".") return
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 2d64fe6 and 7cdc2d2.

📒 Files selected for processing (1)
  • packages/interwovenkit-react/src/components/form/NumericInput.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages: interwovenkit-testnet
🔇 Additional comments (4)
packages/interwovenkit-react/src/components/form/NumericInput.tsx (4)

11-15: LGTM!

The sanitization logic correctly handles multiple decimal points and truncates decimals to the specified precision.


30-30: LGTM!

Local display state correctly separates the visual representation from the form value, enabling intermediate typing states.


47-50: LGTM!

The parsedDisplayValue logic correctly preserves user formatting (like trailing decimals or zeros) while respecting external form value updates.


69-74: LGTM!

The prop spread order correctly allows callers to override defaults, and ref merging with mergeRefs properly combines the field ref with the auto-focus ref.

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.

2 participants