Skip to content

feat: prewarm bridge preview#223

Draft
tansawit wants to merge 17 commits intomainfrom
feat/bridge-preview-prewarm
Draft

feat: prewarm bridge preview#223
tansawit wants to merge 17 commits intomainfrom
feat/bridge-preview-prewarm

Conversation

@tansawit
Copy link
Copy Markdown
Contributor

@tansawit tansawit commented Apr 14, 2026

Summary

Prewarms bridge preview preparation so route and footer state resolve faster and more consistently. Introduces shared preparation logic (preparation.ts, preparedRoute.ts, routeFreshness.ts), refactors bridge footers to consume it, and adds tests for prepared routes, route freshness, and preview footer state.

Merged latest main and reconciled exact fee check with useChainEnabled / shouldCheckExactFee / shouldRunExactFeeQuery from upstream.

Test plan

  • pnpm typecheck
  • pnpm lint --max-warnings=0
  • pnpm test
  • pnpm prettier --check "**/*.{ts,tsx,json,css,md}"
Open in Web Open in Cursor 

Summary by CodeRabbit

  • Bug Fixes

    • Per-route quote freshness policy with stricter freshness checks; preview gating, refresh/retry, and preview error surfaces are more reliable.
    • Approval and fee failures now reliably block preview and surface blocking errors.
  • New Features

    • Centralized bridge preparation and prefetching (address derivation, tx building, ERC‑20 approval discovery, exact‑fee checks) to smooth preview/navigation.
  • Refactor

    • Footer components now consume unified preparation hooks instead of duplicating fetching/formatting logic.
  • Tests

    • Added tests for route freshness and preparation readiness.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a bridge “preparation” layer (address derivation, tx building, approval discovery, exact‑fee checks), dynamic route‑freshness rules, and updates BridgeFields and footer components to delegate, prefetch, and validate using the new hooks and prefetch helpers.

Changes

Cohort / File(s) Summary
Route Freshness
packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts, packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.ts
Introduce BRIDGE_ROUTE_FRESHNESS_MS, getBridgeRouteFreshnessMs, and isBridgeQuoteFresh with tests for L1/L2/same‑chain and cross‑chain freshness.
Preparation Queries & Hooks
packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
New centralized query module exposing query key factories and hooks: useBridgeAddressListQuery, useBridgeTxQuery, useBridgeErc20ApprovalsQuery, useExactFeeCheckQuery; encapsulates address derivation, tx building, allowance checks, fee simulation, gating, and error normalization.
Route Preparation State & Prefetch
packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts, packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts
Add getBridgeRoutePreparationState, useBridgeRoutePreparation, and prefetchBridgeRoutePreparation to compose query outputs into { isPreparing, hasBlockingError, isReady, tx }; tests cover readiness and blocking/error scenarios.
BridgeFields / Submit & Refresh Flow
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx, packages/interwovenkit-react/src/pages/bridge/data/useRouteRefresh.ts
Replace fixed max‑age with dynamic freshness via getBridgeRouteFreshnessMs/isBridgeQuoteFresh; call/prewarm useBridgeRoutePreparation; gate submit on quote freshness, refetch when stale, validate post‑refetch, set preview refresh state/errors, and prefetch preparation before navigation.
Footer: Address List
packages/interwovenkit-react/src/pages/bridge/FooterWithAddressList.tsx
Replace inline address‑derivation and signer handling with useBridgeAddressListQuery; render FooterWithError, loading footer, or children(addressList) based on hook outputs.
Footer: ERC‑20 Approvals
packages/interwovenkit-react/src/pages/bridge/FooterWithErc20Approval.tsx
Replace inline allowance checks with useBridgeErc20ApprovalsQuery(tx); adjust invalidation key to include approvalChainRpc; surface approval errors (inject into cloned children when applicable).
Footer: Exact Fee Check
packages/interwovenkit-react/src/pages/bridge/FooterWithExactFeeCheck.tsx
Delegate fee simulation and balance checks to useExactFeeCheckQuery(route, values, tx); remove local query/helper logic and simplify loading/error gating.
Footer: Bridge Messages
packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx
Replace manual fetch/retry bookkeeping with useBridgeTxQuery; use refetch() to retry after quote refresh and map loading/error states from the hook.
Preview Footer State
packages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.ts, packages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.test.ts
Add early blocking return when approvalError exists in getBridgePreviewFooterState; add test asserting preview blocked by approval error.
Message Request Key / Tests
packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts, packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
Remove exported getBridgeMsgsRequestKey; tests adjusted to use a local deterministic snapshot helper.
Balance helper extraction
packages/interwovenkit-react/src/pages/bridge/data/balance.ts
Extract fetchSkipBalancesForChain helper and simplify useSkipBalancesQuery to return per‑chain denoms directly.
Tests Added
packages/interwovenkit-react/src/pages/bridge/.../preparedRoute.test.ts, .../routeFreshness.test.ts, .../bridgePreviewFooterState.test.ts
New tests covering route freshness, route preparation readiness/error cases, and preview footer approval blocking.

Sequence Diagram

sequenceDiagram
    participant User as User/Form
    participant BridgeFields as BridgeFields
    participant PrepHook as useBridgeRoutePreparation
    participant AddrQ as useBridgeAddressListQuery
    participant TxQ as useBridgeTxQuery
    participant ApprovalsQ as useBridgeErc20ApprovalsQuery
    participant FeeQ as useExactFeeCheckQuery
    participant RouteRef as routeQuery/refetch

    User->>BridgeFields: submit / preview attempt (route, values)
    BridgeFields->>PrepHook: call with route & values

    rect rgba(100,150,200,0.5)
        PrepHook->>AddrQ: fetch intermediary addresses
        AddrQ-->>PrepHook: addressList
        PrepHook->>TxQ: build tx using addressList
        TxQ-->>PrepHook: tx
        alt evm tx requires approvals
            PrepHook->>ApprovalsQ: check ERC‑20 allowances
            ApprovalsQ-->>PrepHook: approvalsNeeded
        end
        alt exact‑fee required
            PrepHook->>FeeQ: simulate gas & check balances
            FeeQ-->>PrepHook: feeCheckResult
        end
    end

    PrepHook-->>BridgeFields: isPreparing, hasBlockingError, isReady, tx

    alt quote is fresh
        BridgeFields->>BridgeFields: skip routeQuery.refetch
    else quote is stale
        BridgeFields->>RouteRef: routeQuery.refetch()
        RouteRef-->>BridgeFields: latestRoute / quoteVerifiedAt
    end

    alt ready & no blocking error
        BridgeFields->>User: navigate /bridge/preview
    else blocking error
        BridgeFields->>User: show error / block preview
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I dug new tunnels, queries set to spring,
Addresses, fees, and approvals — ready to sing.
Freshness checked, txs built with a hop and a cheer,
Preview waits patient — the bridge draws near.
Hop on, little devs, the path is clear!

🚥 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 accurately describes the main change: introducing prewarm logic for bridge preview preparation to improve performance and consistency.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bridge-preview-prewarm

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts (1)

24-26: Extract freshness durations into named constants.

Current logic is correct, but replacing magic numbers here makes intent clearer and reduces drift risk across tests/consumers.

♻️ Suggested refactor
+export const BRIDGE_ROUTE_FRESHNESS_MS = {
+  LAYER_2_SAME_CHAIN: 2000,
+  LAYER_1_SAME_CHAIN: 5000,
+  DEFAULT: 10000,
+} as const
+
 export function getBridgeRouteFreshnessMs({
   srcChainId,
   dstChainId,
   srcChainType,
   layer1ChainId,
 }: BridgeRouteFreshnessParams): number {
   const isSameChainRoute = srcChainId === dstChainId
   const isLayer1Swap = isSameChainRoute && srcChainId === layer1ChainId
   const isLayer2Swap = isSameChainRoute && srcChainType === "initia" && !isLayer1Swap

-  if (isLayer1Swap) return 5000
-  if (isLayer2Swap) return 2000
-  return 10000
+  if (isLayer1Swap) return BRIDGE_ROUTE_FRESHNESS_MS.LAYER_1_SAME_CHAIN
+  if (isLayer2Swap) return BRIDGE_ROUTE_FRESHNESS_MS.LAYER_2_SAME_CHAIN
+  return BRIDGE_ROUTE_FRESHNESS_MS.DEFAULT
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts` around
lines 24 - 26, Extract the magic numeric durations into descriptive constants
and use them in the return paths of the route freshness logic: define constants
like LAYER1_SWAP_FRESHNESS_MS = 5000, LAYER2_SWAP_FRESHNESS_MS = 2000, and
DEFAULT_ROUTE_FRESHNESS_MS = 10000 (or similar names) near the top of the module
and replace the literal numbers in the branch that currently returns
5000/2000/10000 (the route freshness decision in this module) with those
constants so intent is clear and values are centralized for tests/consumers.
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx (1)

141-144: Side-effect hook for prewarming - return value intentionally unused.

This invocation triggers background queries (address list, tx, fee check, approvals) to populate the cache before navigation to /bridge/preview. The return value is intentionally discarded since this is purely for prewarming.

Consider adding a brief comment explaining this is a prewarming side effect to prevent future developers from removing it as "unused code":

📝 Suggested comment
+  // Prewarm preparation queries so preview page loads faster
   useBridgeRoutePreparation({
     route,
     values,
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx` around lines
141 - 144, The call to useBridgeRoutePreparation({ route, values }) is a
deliberate side-effect that prewarms cache (address list, tx, fee check,
approvals) and its return value is intentionally unused; add a short explanatory
comment directly above this invocation stating that this hook is used for
prewarming prior to navigation to /bridge/preview and that the return value is
intentionally discarded to prevent future refactors from removing it. Reference
the useBridgeRoutePreparation call and the route/values props in the comment so
future readers know why this seemingly unused invocation must remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx`:
- Around line 141-144: The call to useBridgeRoutePreparation({ route, values })
is a deliberate side-effect that prewarms cache (address list, tx, fee check,
approvals) and its return value is intentionally unused; add a short explanatory
comment directly above this invocation stating that this hook is used for
prewarming prior to navigation to /bridge/preview and that the return value is
intentionally discarded to prevent future refactors from removing it. Reference
the useBridgeRoutePreparation call and the route/values props in the comment so
future readers know why this seemingly unused invocation must remain.

In `@packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts`:
- Around line 24-26: Extract the magic numeric durations into descriptive
constants and use them in the return paths of the route freshness logic: define
constants like LAYER1_SWAP_FRESHNESS_MS = 5000, LAYER2_SWAP_FRESHNESS_MS = 2000,
and DEFAULT_ROUTE_FRESHNESS_MS = 10000 (or similar names) near the top of the
module and replace the literal numbers in the branch that currently returns
5000/2000/10000 (the route freshness decision in this module) with those
constants so intent is clear and values are centralized for tests/consumers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09188a36-82a8-423f-a88c-34dcf98f6c93

📥 Commits

Reviewing files that changed from the base of the PR and between fff8722 and 8dfb8db.

📒 Files selected for processing (13)
  • packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
  • packages/interwovenkit-react/src/pages/bridge/FooterWithAddressList.tsx
  • packages/interwovenkit-react/src/pages/bridge/FooterWithErc20Approval.tsx
  • packages/interwovenkit-react/src/pages/bridge/FooterWithExactFeeCheck.tsx
  • packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx
  • packages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.test.ts
  • packages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.ts
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts
  • packages/interwovenkit-react/src/pages/bridge/data/useRouteRefresh.ts

Resolve FooterWithExactFeeCheck conflict by aligning useExactFeeCheckQuery
with main: useChainEnabled, shouldCheckExactFee, and shouldRunExactFeeQuery.
Expose chain loading and errors for preview preparation state.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 14, 2026

Deploying interwovenkit-staging with  Cloudflare Pages  Cloudflare Pages

Latest commit: a199134
Status: ✅  Deploy successful!
Preview URL: https://6b6ff07e.interwovenkit-staging.pages.dev
Branch Preview URL: https://feat-bridge-preview-prewarm.interwovenkit-staging.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 14, 2026

Deploying interwovenkit with  Cloudflare Pages  Cloudflare Pages

Latest commit: a199134
Status: ✅  Deploy successful!
Preview URL: https://a7a575b5.interwovenkit.pages.dev
Branch Preview URL: https://feat-bridge-preview-prewarm.interwovenkit.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 14, 2026

Deploying interwovenkit-testnet with  Cloudflare Pages  Cloudflare Pages

Latest commit: a199134
Status: ✅  Deploy successful!
Preview URL: https://fcd13731.interwovenkit-testnet.pages.dev
Branch Preview URL: https://feat-bridge-preview-prewarm.interwovenkit-testnet.pages.dev

View logs

Export BRIDGE_ROUTE_FRESHNESS_MS and use it in getBridgeRouteFreshnessMs and
tests. Document the intentional side-effect-only useBridgeRoutePreparation call
on the bridge fields page.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Dead production export after refactor removes only caller
    • Removed the unused getBridgeMsgsRequestKey export and its dead test coverage now that production code no longer references it.
  • ✅ Fixed: Unnecessary factory wrapper always immediately invoked
    • Simplified getBridgeAddressList into a direct async function and updated its only caller to await it without the redundant closure.

Create PR

Or push these changes by commenting:

@cursor push eadab8cab3
Preview (eadab8cab3)
diff --git a/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts b/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
--- a/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
@@ -1,26 +1,5 @@
-import {
-  getBridgeMsgsRequestKey,
-  shouldRetryBridgeMsgsAfterQuoteRefresh,
-} from "./data/messageRequestKey"
+import { shouldRetryBridgeMsgsAfterQuoteRefresh } from "./data/messageRequestKey"
 
-describe("getBridgeMsgsRequestKey", () => {
-  it("is stable when route inputs stay the same", () => {
-    expect(
-      getBridgeMsgsRequestKey({
-        addressList: ["init1test"],
-        operations: [{ transfer: "same-route" }],
-        signedOpHook: { signer: "init1test", hook: "hook" },
-      }),
-    ).toBe(
-      getBridgeMsgsRequestKey({
-        addressList: ["init1test"],
-        operations: [{ transfer: "same-route" }],
-        signedOpHook: { signer: "init1test", hook: "hook" },
-      }),
-    )
-  })
-})
-
 describe("shouldRetryBridgeMsgsAfterQuoteRefresh", () => {
   it("retries when a refreshed quote arrives while preserving a stale preview", () => {
     expect(

diff --git a/packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts b/packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts
--- a/packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts
@@ -1,21 +1,3 @@
-import type { SignedOpHook } from "./tx"
-
-export function getBridgeMsgsRequestKey({
-  addressList,
-  operations,
-  signedOpHook,
-}: {
-  addressList: string[]
-  operations: unknown
-  signedOpHook?: SignedOpHook
-}): string {
-  return JSON.stringify({
-    addressList,
-    operations,
-    signedOpHook: signedOpHook ?? null,
-  })
-}
-
 export function shouldRetryBridgeMsgsAfterQuoteRefresh({
   previousQuoteVerifiedAt,
   quoteVerifiedAt,

diff --git a/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts b/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
--- a/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
@@ -116,7 +116,7 @@
   signer: ReturnType<typeof useOfflineSigner>
   findSkipChain: ReturnType<typeof useFindSkipChain>
   findChainType: ReturnType<typeof useFindChainType>
-}): () => Promise<string[]> {
+}): Promise<string[]> {
   const { required_chain_addresses } = route
   const { srcChainId, dstChainId, sender, recipient } = values
 
@@ -129,43 +129,41 @@
     findChainType,
   })
 
-  return async () => {
-    let pubkey: Uint8Array | undefined
+  let pubkey: Uint8Array | undefined
 
-    if (isPubkeyRequired) {
-      if (!signer) throw new Error("Wallet not connected")
-      const [{ pubkey: signerPubkey }] = await signer.getAccounts()
-      pubkey = signerPubkey
+  if (isPubkeyRequired) {
+    if (!signer) throw new Error("Wallet not connected")
+    const [{ pubkey: signerPubkey }] = await signer.getAccounts()
+    pubkey = signerPubkey
+  }
+
+  return required_chain_addresses.map((chainId, index) => {
+    if (index === required_chain_addresses.length - 1) {
+      const dstChain = findSkipChain(dstChainId)
+      const dstChainType = findChainType(dstChain)
+      if (dstChainType === "initia") return InitiaAddress(recipient).bech32
+      return recipient
     }
 
-    return required_chain_addresses.map((chainId, index) => {
-      if (index === required_chain_addresses.length - 1) {
-        const dstChain = findSkipChain(dstChainId)
-        const dstChainType = findChainType(dstChain)
-        if (dstChainType === "initia") return InitiaAddress(recipient).bech32
-        return recipient
-      }
+    const chain = findSkipChain(chainId)
+    const chainType = findChainType(chain)
 
-      const chain = findSkipChain(chainId)
-      const chainType = findChainType(chain)
-
-      switch (chainType) {
-        case "initia":
-          return initiaAddress
-        case "evm":
-          return hexAddress
-        case "cosmos": {
-          if (srcChainType === "cosmos") {
-            return toBech32(chain.bech32_prefix, fromBech32(sender).data)
-          }
-          if (!pubkey) throw new Error("Pubkey not found")
-          return pubkeyToAddress(encodeSecp256k1Pubkey(pubkey), chain.bech32_prefix)
+    switch (chainType) {
+      case "initia":
+        return initiaAddress
+      case "evm":
+        return hexAddress
+      case "cosmos": {
+        if (srcChainType === "cosmos") {
+          return toBech32(chain.bech32_prefix, fromBech32(sender).data)
         }
-        default:
-          throw new Error("Unlisted chain type")
+        if (!pubkey) throw new Error("Pubkey not found")
+        return pubkeyToAddress(encodeSecp256k1Pubkey(pubkey), chain.bech32_prefix)
       }
-    })
-  }
+      default:
+        throw new Error("Unlisted chain type")
+    }
+  })
 }
 
 function hasCosmosIntermediary({
@@ -274,7 +272,7 @@
           signer,
           findSkipChain,
           findChainType,
-        })()
+        })
       } catch (error) {
         throw await normalizeError(error)
       }

You can send follow-ups to the cloud agent here.

Comment thread packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsx Outdated
Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

141-146: Direct post-refresh navigation still skips prewarming for the new route.

useBridgeRoutePreparation() only runs for route from render. When refetch() returns a newer latestRoute here and you navigate immediately, React never gets a render with that new route, so its address/tx/fee/approval queries stay cold on the direct-preview path.

If you want stale-quote submits to get the same hot-preview benefit, extract an imperative prefetch helper from preparedRoute.ts and await it for latestRoute before the direct navigate().

Also applies to: 176-188, 234-238

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx` around lines
141 - 146, useBridgeRoutePreparation only prewarms based on the route seen
during render, so when refetch() yields a newer latestRoute and you call
navigate() immediately the necessary address/tx/fee/approval queries never
prefetch; to fix, extract an imperative prefetch helper from preparedRoute.ts
(e.g., prefetchPreparedRoute or similar) that accepts a route and performs the
same query preloads, then in the places that handle stale-quote submits (the
code paths that call refetch() and then navigate(), referencing latestRoute and
navigate()) await that prefetch helper for latestRoute before calling
navigate(); keep useBridgeRoutePreparation for render-driven prewarming but
invoke the new imperative prefetch in the direct-preview submit flows (also
update the other occurrences noted).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx`:
- Around line 161-168: The freshness check in submit (the handler created by
handleSubmit) can reuse a routeQuery keyed by debouncedQuantity while the form's
raw values are still un-debounced; to fix, ensure you only treat a cached route
as fresh when the current form amount matches the debounced key used by
routeQuery (e.g., compare values.quantity (or values.amount) to
debouncedQuantity) or otherwise block navigation until debounce has settled;
update the logic around isBridgeQuoteFresh/routeRefreshMs/route/quoteVerifiedAt
in submit (and the same check in the code block covering the 170-195 range) to
require values.quantity === debouncedQuantity (or disable the Preview button
until they match) before navigating to /bridge/preview.

---

Nitpick comments:
In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx`:
- Around line 141-146: useBridgeRoutePreparation only prewarms based on the
route seen during render, so when refetch() yields a newer latestRoute and you
call navigate() immediately the necessary address/tx/fee/approval queries never
prefetch; to fix, extract an imperative prefetch helper from preparedRoute.ts
(e.g., prefetchPreparedRoute or similar) that accepts a route and performs the
same query preloads, then in the places that handle stale-quote submits (the
code paths that call refetch() and then navigate(), referencing latestRoute and
navigate()) await that prefetch helper for latestRoute before calling
navigate(); keep useBridgeRoutePreparation for render-driven prewarming but
invoke the new imperative prefetch in the direct-preview submit flows (also
update the other occurrences noted).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6fe6c61-7002-4941-aa9f-b7af868e8946

📥 Commits

Reviewing files that changed from the base of the PR and between 8d36d69 and 6a8ee74.

📒 Files selected for processing (3)
  • packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.ts
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/interwovenkit-react/src/pages/bridge/data/routeFreshness.ts

Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx Outdated
Guard preview submit when quantity lags debounced simulation. Prefetch address
list, bridge tx, and ERC-20 approvals before navigating so refreshed routes
still warm the cache. Extract shared query option builders, flatten address list
fetch to a single async function, remove unused getBridgeMsgsRequestKey export,
and add fetchSkipBalancesForChain for reuse.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.test.ts (1)

23-33: ⚠️ Potential issue | 🟠 Major

Stability assertion is tautological and won’t catch regressions.

Line 23 and Line 29 call the same local helper with the same input on both sides of toBe, so this test effectively cannot fail. Assert against a fixed expected value (or, preferably, against the real production query-key builder).

Suggested minimal fix
   it("is stable when route inputs stay the same", () => {
-    expect(
-      bridgeMsgsRequestKeySnapshot({
-        addressList: ["init1test"],
-        operations: [{ transfer: "same-route" }],
-        signedOpHook: { signer: "init1test", hook: "hook" },
-      }),
-    ).toBe(
-      bridgeMsgsRequestKeySnapshot({
-        addressList: ["init1test"],
-        operations: [{ transfer: "same-route" }],
-        signedOpHook: { signer: "init1test", hook: "hook" },
-      }),
-    )
+    expect(
+      bridgeMsgsRequestKeySnapshot({
+        addressList: ["init1test"],
+        operations: [{ transfer: "same-route" }],
+        signedOpHook: { signer: "init1test", hook: "hook" },
+      }),
+    ).toBe(
+      '{"addressList":["init1test"],"operations":[{"transfer":"same-route"}],"signedOpHook":{"signer":"init1test","hook":"hook"}}',
+    )
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts` around
lines 23 - 33, The test currently compares bridgeMsgsRequestKeySnapshot(...) to
itself, making the assertion tautological; replace the right-hand side with a
stable expected value or call the real production query-key builder instead of
the helper to validate output. Locate the test using the
bridgeMsgsRequestKeySnapshot helper in FooterWithMsgs.test.ts and change the
assertion so the matcher checks against a fixed expected key (literal or
fixture) or against the production function that builds query keys (e.g., the
actual query-key builder used at runtime) to ensure the test fails on
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts`:
- Around line 23-33: The test currently compares
bridgeMsgsRequestKeySnapshot(...) to itself, making the assertion tautological;
replace the right-hand side with a stable expected value or call the real
production query-key builder instead of the helper to validate output. Locate
the test using the bridgeMsgsRequestKeySnapshot helper in FooterWithMsgs.test.ts
and change the assertion so the matcher checks against a fixed expected key
(literal or fixture) or against the production function that builds query keys
(e.g., the actual query-key builder used at runtime) to ensure the test fails on
regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 116cca4c-3051-42b2-90c4-0e67ebbf843f

📥 Commits

Reviewing files that changed from the base of the PR and between 6a8ee74 and b798c62.

📒 Files selected for processing (6)
  • packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
  • packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
  • packages/interwovenkit-react/src/pages/bridge/data/balance.ts
  • packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
💤 Files with no reviewable changes (1)
  • packages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
  • packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
  • packages/interwovenkit-react/src/pages/bridge/data/preparation.ts

Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
Comment thread packages/interwovenkit-react/src/pages/bridge/data/balance.ts
Make fetchSkipBalancesForChain module-private. Align preview prefetch error
handling with project conventions.
Assert a fixed JSON string and add a case where differing hooks produce
different keys.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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/BridgeFields.tsx (1)

81-82: ⚠️ Potential issue | 🟠 Major

Block repeat submits during preview preparation.

Lines 216-237 await prefetchBridgeRoutePreparation(...), but the UI only disables on previewRefreshing, which is set in the stale-route refetch branch. On a fresh route, a double click can run the submit path twice, issuing duplicate preparation requests and potentially opening the warning modal or navigating twice.

Suggested fix
   const [previewRefreshError, setPreviewRefreshError] = useState<string | undefined>(undefined)
-  const [previewRefreshing, setPreviewRefreshing] = useState(false)
+  const [previewPending, setPreviewPending] = useState(false)

   const submit = handleSubmit(async (values: FormValues) => {
+    if (previewPending) return
     setPreviewRefreshError(undefined)
+    setPreviewPending(true)
+    try {
       if (values.quantity !== debouncedQuantity) {
         setPreviewRefreshError(
           "Route is still updating for this amount. Please wait a moment and try again.",
         )
         return
       }

       const isFreshRoute =
         !!route &&
         isBridgeQuoteFresh({
           freshnessMs: routeRefreshMs,
           quoteVerifiedAt: routeQuery.dataUpdatedAt,
         })

       let latestRoute = route
       let quoteVerifiedAt = routeQuery.dataUpdatedAt

       if (!isFreshRoute) {
-        setPreviewRefreshing(true)
-        try {
-          const result = await routeQuery.refetch()
-          if (result.error || !result.data || !result.dataUpdatedAt) {
-            setPreviewRefreshError(
-              result.error instanceof Error
-                ? result.error.message
-                : "Failed to refresh route. Please try again.",
-            )
-            return
-          }
-
-          latestRoute = result.data
-          quoteVerifiedAt = result.dataUpdatedAt
-        } finally {
-          setPreviewRefreshing(false)
+        const result = await routeQuery.refetch()
+        if (result.error || !result.data || !result.dataUpdatedAt) {
+          setPreviewRefreshError(
+            result.error instanceof Error
+              ? result.error.message
+              : "Failed to refresh route. Please try again.",
+          )
+          return
         }
+
+        latestRoute = result.data
+        quoteVerifiedAt = result.dataUpdatedAt
       }

       await prefetchBridgeRoutePreparation({
         queryClient,
         skip,
@@
       })
+    } finally {
+      setPreviewPending(false)
+    }
   })

   const previewButtonLoading = useMemo(() => {
-    if (previewRefreshing) return "Refreshing route..."
+    if (previewPending) return "Preparing preview..."
     if (isSimulating) return "Simulating..."
     return false
-  }, [isSimulating, previewRefreshing])
+  }, [isSimulating, previewPending])

   <Button.White
     loading={previewButtonLoading}
-    disabled={!!disabledMessage || previewRefreshing}
+    disabled={!!disabledMessage || previewPending}
   >

Also applies to: 172-237, 408-412, 615-618

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx` around lines
81 - 82, The submit path can be invoked twice on fresh routes because
previewRefreshing is only set in the stale-route branch; to fix, mark the UI as
busy at the start of every preview preparation path and guard against re-entry:
in the handler that calls prefetchBridgeRoutePreparation(...) set
previewRefreshing to true immediately (via setPreviewRefreshing(true)), check
previewRefreshing at the top and return early if already true, wrap the async
call in try/finally to clear previewRefreshing (setPreviewRefreshing(false)) and
setPreviewRefreshError on failure; apply the same pattern wherever
prefetchBridgeRoutePreparation is invoked to prevent duplicate requests.
🧹 Nitpick comments (1)
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx (1)

216-233: Extract the duplicated preparation-prefetch call.

The main submit path and the warning-confirm path build the same prefetchBridgeRoutePreparation(...) payload twice. A small local helper would keep both branches aligned and reduce copy/paste drift the next time this contract changes.

Refactor sketch
+  const prefetchPreparedRoute = useCallback(
+    (route: typeof latestRoute, values: FormValues) =>
+      prefetchBridgeRoutePreparation({
+        queryClient,
+        skip,
+        route,
+        values: {
+          srcChainId: values.srcChainId,
+          dstChainId: values.dstChainId,
+          sender: values.sender,
+          recipient: values.recipient,
+          slippagePercent: values.slippagePercent,
+        },
+        initiaAddress,
+        hexAddress,
+        signer,
+        findSkipChain,
+        findChainType,
+      }),
+    [queryClient, skip, initiaAddress, hexAddress, signer, findSkipChain, findChainType],
+  )
...
-      await prefetchBridgeRoutePreparation({ ... })
+      await prefetchPreparedRoute(latestRoute, values)
...
-                  await prefetchBridgeRoutePreparation({ ... })
+                  await prefetchPreparedRoute(latestRoute, values)

Also applies to: 259-276

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx` around lines
216 - 233, The code duplicates the call to prefetchBridgeRoutePreparation with
the same payload in two places (main submit path and the warning-confirm path);
extract a small local helper (e.g., buildAndPrefetchBridgePreparation or
callPrefetchBridgeRoutePreparation) that constructs the payload using
latestRoute, values.{srcChainId,dstChainId,sender,recipient,slippagePercent},
initiaAddress, hexAddress, signer, findSkipChain, and findChainType and then
calls prefetchBridgeRoutePreparation; replace both direct calls with this helper
so both branches use the single shared implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx`:
- Around line 81-82: The submit path can be invoked twice on fresh routes
because previewRefreshing is only set in the stale-route branch; to fix, mark
the UI as busy at the start of every preview preparation path and guard against
re-entry: in the handler that calls prefetchBridgeRoutePreparation(...) set
previewRefreshing to true immediately (via setPreviewRefreshing(true)), check
previewRefreshing at the top and return early if already true, wrap the async
call in try/finally to clear previewRefreshing (setPreviewRefreshing(false)) and
setPreviewRefreshError on failure; apply the same pattern wherever
prefetchBridgeRoutePreparation is invoked to prevent duplicate requests.

---

Nitpick comments:
In `@packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx`:
- Around line 216-233: The code duplicates the call to
prefetchBridgeRoutePreparation with the same payload in two places (main submit
path and the warning-confirm path); extract a small local helper (e.g.,
buildAndPrefetchBridgePreparation or callPrefetchBridgeRoutePreparation) that
constructs the payload using latestRoute,
values.{srcChainId,dstChainId,sender,recipient,slippagePercent}, initiaAddress,
hexAddress, signer, findSkipChain, and findChainType and then calls
prefetchBridgeRoutePreparation; replace both direct calls with this helper so
both branches use the single shared implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa4bf10c-e58c-4983-b85b-1fb22c96b9dc

📥 Commits

Reviewing files that changed from the base of the PR and between b798c62 and 6efe28e.

📒 Files selected for processing (2)
  • packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
  • packages/interwovenkit-react/src/pages/bridge/data/balance.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/interwovenkit-react/src/pages/bridge/data/balance.ts

Add a ref lock and previewSubmitting state so double-clicks cannot overlap
prefetch and navigation while the route is already fresh. Extend the preview
button disabled and loading states to cover preparation work.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Prefetch unconditionally fetches tx for op-hook routes
    • Prefetch now stops after deriving addresses when an OP-hook route has not been signed yet, so preview navigation no longer triggers a premature tx fetch.
  • ✅ Fixed: Duplicate prefetch in warning modal closure is redundant
    • The submit flow now reuses one shared prefetch helper and only runs the initial prefetch for routes without warnings, avoiding redundant work before the warning modal.

Create PR

Or push these changes by commenting:

@cursor push 0868237339
Preview (0868237339)
diff --git a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
--- a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
+++ b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
@@ -218,30 +218,35 @@
         setPreviewRefreshError("Failed to refresh route. Please try again.")
         return
       }
-
-      try {
-        await prefetchBridgeRoutePreparation({
-          queryClient,
-          skip,
-          route: latestRoute,
-          values: {
-            srcChainId: values.srcChainId,
-            dstChainId: values.dstChainId,
-            sender: values.sender,
-            recipient: values.recipient,
-            slippagePercent: values.slippagePercent,
-          },
-          initiaAddress,
-          hexAddress,
-          signer,
-          findSkipChain,
-          findChainType,
-        })
-      } catch (error) {
-        setPreviewRefreshError((await normalizeError(error)).message)
-        return
+      const previewPreparationParams = {
+        queryClient,
+        skip,
+        route: latestRoute,
+        values: {
+          srcChainId: values.srcChainId,
+          dstChainId: values.dstChainId,
+          sender: values.sender,
+          recipient: values.recipient,
+          slippagePercent: values.slippagePercent,
+        },
+        initiaAddress,
+        hexAddress,
+        signer,
+        findSkipChain,
+        findChainType,
       }
+      const prefetchPreviewPreparation = async () => {
+        try {
+          await prefetchBridgeRoutePreparation(previewPreparationParams)
+          return true
+        } catch (error) {
+          setPreviewRefreshError((await normalizeError(error)).message)
+          return false
+        }
+      }
 
+      if (!latestRoute.warning && !(await prefetchPreviewPreparation())) return
+
       track("Bridge Simulation Success", {
         quantity: values.quantity,
         srcChainId: values.srcChainId,
@@ -262,26 +267,7 @@
               secondaryButton={{
                 label: "Proceed anyway",
                 onClick: async () => {
-                  try {
-                    await prefetchBridgeRoutePreparation({
-                      queryClient,
-                      skip,
-                      route: latestRoute,
-                      values: {
-                        srcChainId: values.srcChainId,
-                        dstChainId: values.dstChainId,
-                        sender: values.sender,
-                        recipient: values.recipient,
-                        slippagePercent: values.slippagePercent,
-                      },
-                      initiaAddress,
-                      hexAddress,
-                      signer,
-                      findSkipChain,
-                      findChainType,
-                    })
-                  } catch (error) {
-                    setPreviewRefreshError((await normalizeError(error)).message)
+                  if (!(await prefetchPreviewPreparation())) {
                     closeModal()
                     return
                   }

diff --git a/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts b/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts
--- a/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.ts
@@ -1,5 +1,5 @@
 import type { TxJson } from "@skip-go/client"
-import { getBridgeRoutePreparationState } from "./preparedRoute"
+import { getBridgeRoutePreparationState, prefetchBridgeRoutePreparation } from "./preparedRoute"
 import type { RouterRouteResponseJson } from "./simulate"
 
 function createRoute(overrides?: Partial<RouterRouteResponseJson>): RouterRouteResponseJson {
@@ -9,6 +9,7 @@
     dest_asset_chain_id: "initia-1",
     dest_asset_denom: "uinit",
     operations: [],
+    required_chain_addresses: ["arb-1", "initia-1"],
     source_asset_chain_id: "arb-1",
     source_asset_denom: "0xtoken",
     ...overrides,
@@ -168,3 +169,61 @@
     )
   })
 })
+
+describe("prefetchBridgeRoutePreparation", () => {
+  it("skips tx prefetch until the required op hook is signed", async () => {
+    const fetchQuery = vi.fn().mockResolvedValueOnce(["0xsigner"])
+
+    await prefetchBridgeRoutePreparation({
+      queryClient: { fetchQuery } as never,
+      skip: {} as never,
+      route: createRoute({ required_op_hook: true }),
+      values: {
+        srcChainId: "arb-1",
+        dstChainId: "initia-1",
+        sender: "0xsender",
+        recipient: "initia1recipient",
+        slippagePercent: "1",
+      },
+      initiaAddress: "initia1sender",
+      hexAddress: "0xsender",
+      signer: {} as never,
+      findSkipChain: vi.fn(),
+      findChainType: vi.fn(),
+    })
+
+    expect(fetchQuery).toHaveBeenCalledTimes(1)
+  })
+
+  it("continues tx prefetch once the required op hook is signed", async () => {
+    const fetchQuery = vi
+      .fn()
+      .mockResolvedValueOnce(["0xsigner"])
+      .mockResolvedValueOnce(createEvmTx())
+      .mockResolvedValueOnce([])
+
+    await prefetchBridgeRoutePreparation({
+      queryClient: { fetchQuery } as never,
+      skip: {} as never,
+      route: createRoute({ required_op_hook: true }),
+      values: {
+        srcChainId: "arb-1",
+        dstChainId: "initia-1",
+        sender: "0xsender",
+        recipient: "initia1recipient",
+        slippagePercent: "1",
+      },
+      signedOpHook: {
+        signer: "initia1sender",
+        hook: "signed-hook",
+      },
+      initiaAddress: "initia1sender",
+      hexAddress: "0xsender",
+      signer: {} as never,
+      findSkipChain: vi.fn().mockReturnValue({ rpc: "https://rpc.example" }),
+      findChainType: vi.fn(),
+    })
+
+    expect(fetchQuery).toHaveBeenCalledTimes(3)
+  })
+})

diff --git a/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts b/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
--- a/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
@@ -124,6 +124,8 @@
       findChainType,
     }),
   )
+  if (route.required_op_hook && !signedOpHook) return
+
   const tx = await queryClient.fetchQuery(
     createBridgeTxQueryOptions({
       skip,

You can send follow-ups to the cloud agent here.

Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx Outdated
fetchQuery ignores React Query enabled flags; guard prefetch like
useBridgeTxQuery so required_op_hook routes do not call fetchBridgeTxs early.
Prefetch only on the direct preview path to avoid duplicate work when a
warning modal defers navigation. Add a unit test for the op-hook case.
Comment thread packages/interwovenkit-react/src/pages/bridge/data/useRouteRefresh.ts Outdated
Preview staleness should use the default quote window, not the shorter
same-chain L1/L2 intervals used for BridgeFields simulation polling.
Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx Outdated
Comment thread packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts Outdated
Defer releasing the submit lock when opening the route warning modal so
double-clicks cannot start a second submit while the user decides. Release on
Cancel, prefetch failure, or after navigation from Proceed anyway.

Drop the local-only request key snapshot tests; shouldRetryBridgeMsgsAfterQuoteRefresh
coverage remains.
Unmount cleanup covers backdrop and overlay close paths, not only Cancel.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Exported function only used within defining file
    • Removed the unnecessary export so the route-key helper remains a file-local implementation detail.

Create PR

Or push these changes by commenting:

@cursor push 2e5e0f0840
Preview (2e5e0f0840)
diff --git a/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts b/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
--- a/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
@@ -73,7 +73,7 @@
   }) => [params],
 })
 
-export function getBridgePreparationRouteKey(route: RouterRouteResponseJson): string {
+function getBridgePreparationRouteKey(route: RouterRouteResponseJson): string {
   return JSON.stringify({
     amount_in: route.amount_in,
     amount_out: route.amount_out,

You can send follow-ups to the cloud agent here.

Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
When approvalError is set but children is not a valid element, wrap with
Footer + FormHelp instead of returning children unchanged. Restore explicit
op-hook guard in createBridgeTxQueryOptions queryFn for parity with fetchQuery
bypassing enabled.
Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts
Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparedRoute.ts Outdated
Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
Comment thread packages/interwovenkit-react/src/pages/bridge/data/preparation.ts
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 14, 2026

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Submit lock released before modal callback completes
    • The preview submit lock now stays active until the warning modal closes by releasing it from a modal onClose callback instead of the submit finally block.
  • ✅ Fixed: Tests validate local helper, not production code
    • The dead local helper assertions were removed so the test file only covers the remaining production retry logic.

Create PR

Or push these changes by commenting:

@cursor push 67f5f81672
Preview (67f5f81672)
diff --git a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
--- a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
+++ b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
@@ -82,6 +82,10 @@
   const [previewRefreshing, setPreviewRefreshing] = useState(false)
   const [previewSubmitting, setPreviewSubmitting] = useState(false)
   const previewSubmitLock = useRef(false)
+  const releasePreviewSubmit = useCallback(() => {
+    previewSubmitLock.current = false
+    setPreviewSubmitting(false)
+  }, [])
 
   const [selectedType, setSelectedType] = useLocalStorage<RouteType>(
     LocalStorageKey.BRIDGE_ROUTE_TYPE,
@@ -176,6 +180,7 @@
     previewSubmitLock.current = true
     setPreviewSubmitting(true)
     setPreviewRefreshError(undefined)
+    let releaseOnFinally = true
     try {
       if (values.quantity !== debouncedQuantity) {
         setPreviewRefreshError(
@@ -229,7 +234,9 @@
 
       if (latestRoute.warning) {
         const { type = "", message } = latestRoute.warning ?? {}
+        releaseOnFinally = false
         openModal({
+          onClose: releasePreviewSubmit,
           content: (
             <PlainModalContent
               type="warning"
@@ -307,8 +314,7 @@
         quoteVerifiedAt,
       })
     } finally {
-      previewSubmitLock.current = false
-      setPreviewSubmitting(false)
+      if (releaseOnFinally) releasePreviewSubmit()
     }
   })
 

diff --git a/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts b/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
--- a/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
+++ b/packages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.ts
@@ -1,50 +1,5 @@
 import { shouldRetryBridgeMsgsAfterQuoteRefresh } from "./data/messageRequestKey"
-import type { SignedOpHook } from "./data/tx"
 
-function bridgeMsgsRequestKeySnapshot({
-  addressList,
-  operations,
-  signedOpHook,
-}: {
-  addressList: string[]
-  operations: unknown
-  signedOpHook?: SignedOpHook
-}): string {
-  return JSON.stringify({
-    addressList,
-    operations,
-    signedOpHook: signedOpHook ?? null,
-  })
-}
-
-describe("bridgeMsgsRequestKeySnapshot", () => {
-  it("serializes inputs deterministically", () => {
-    expect(
-      bridgeMsgsRequestKeySnapshot({
-        addressList: ["init1test"],
-        operations: [{ transfer: "same-route" }],
-        signedOpHook: { signer: "init1test", hook: "hook" },
-      }),
-    ).toBe(
-      '{"addressList":["init1test"],"operations":[{"transfer":"same-route"}],"signedOpHook":{"signer":"init1test","hook":"hook"}}',
-    )
-  })
-
-  it("changes when hook inputs change", () => {
-    const a = bridgeMsgsRequestKeySnapshot({
-      addressList: ["init1test"],
-      operations: [{ transfer: "same-route" }],
-      signedOpHook: { signer: "init1test", hook: "hook-a" },
-    })
-    const b = bridgeMsgsRequestKeySnapshot({
-      addressList: ["init1test"],
-      operations: [{ transfer: "same-route" }],
-      signedOpHook: { signer: "init1test", hook: "hook-b" },
-    })
-    expect(a).not.toBe(b)
-  })
-})
-
 describe("shouldRetryBridgeMsgsAfterQuoteRefresh", () => {
   it("retries when a refreshed quote arrives while preserving a stale preview", () => {
     expect(

diff --git a/packages/interwovenkit-react/src/public/app/ModalContext.ts b/packages/interwovenkit-react/src/public/app/ModalContext.ts
--- a/packages/interwovenkit-react/src/public/app/ModalContext.ts
+++ b/packages/interwovenkit-react/src/public/app/ModalContext.ts
@@ -6,6 +6,7 @@
   title?: string
   content?: ReactNode
   path?: string
+  onClose?: () => void
 }
 
 interface ModalContextProps {

diff --git a/packages/interwovenkit-react/src/public/app/ModalProvider.tsx b/packages/interwovenkit-react/src/public/app/ModalProvider.tsx
--- a/packages/interwovenkit-react/src/public/app/ModalProvider.tsx
+++ b/packages/interwovenkit-react/src/public/app/ModalProvider.tsx
@@ -1,4 +1,4 @@
-import { type PropsWithChildren, useCallback, useState } from "react"
+import { type PropsWithChildren, useCallback, useRef, useState } from "react"
 import { useAtomValue } from "jotai"
 import AsyncBoundary from "@/components/AsyncBoundary"
 import Modal from "@/components/Modal"
@@ -10,27 +10,37 @@
 const ModalProvider = ({ children }: PropsWithChildren) => {
   const [{ title, content, path }, setOptions] = useState<ModalOptions>({})
   const [isOpen, setIsOpen] = useState(false)
+  const optionsRef = useRef<ModalOptions>({})
   const txRequest = useAtomValue(txRequestHandlerAtom)
 
+  const runOnClose = useCallback(() => {
+    const { onClose } = optionsRef.current
+    optionsRef.current = {}
+    onClose?.()
+  }, [])
+
   const openModal = useCallback((options: ModalOptions) => {
+    optionsRef.current = options
     setOptions(options)
     setIsOpen(true)
   }, [])
 
   const closeModal = useCallback(() => {
+    runOnClose()
     setOptions({})
     setIsOpen(false)
-  }, [])
+  }, [runOnClose])
 
   const handleOpenChange = useCallback(
     (open: boolean) => {
       if (!open) {
         txRequest?.reject(new Error("User rejected"))
+        runOnClose()
         setOptions({})
       }
       setIsOpen(open)
     },
-    [txRequest],
+    [runOnClose, txRequest],
   )
 
   return (

You can send follow-ups to the cloud agent here.

…module

Rename messageRequestKey to quoteRefreshRetry, merge duplicate signer imports,
drop redundant staleTime in useBridgeTxQuery, add cross-references between
bridge tx cache keys and preview route signatures, and expose
useBridgeRoutePreparationPrewarm for side-effect-only preparation.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Hardcoded fallback discards original error value
    • Updated the bridge route refresh fallback to preserve non-Error values via String(result.error) while keeping the generic message when no error is present.

Create PR

Or push these changes by commenting:

@cursor push 28a940bd1a
Preview (28a940bd1a)
diff --git a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
--- a/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
+++ b/packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
@@ -325,9 +325,11 @@
           const result = await routeQuery.refetch()
           if (result.error || !result.data || !result.dataUpdatedAt) {
             setPreviewRefreshError(
-              result.error instanceof Error
-                ? result.error.message
-                : "Failed to refresh route. Please try again.",
+              result.error == null
+                ? "Failed to refresh route. Please try again."
+                : result.error instanceof Error
+                  ? result.error.message
+                  : `Failed to refresh route. Please try again. ${String(result.error)}`,
             )
             return
           }

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit f15fec9. Configure here.

Comment thread packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsx
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