Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 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
📒 Files selected for processing (13)
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsxpackages/interwovenkit-react/src/pages/bridge/FooterWithAddressList.tsxpackages/interwovenkit-react/src/pages/bridge/FooterWithErc20Approval.tsxpackages/interwovenkit-react/src/pages/bridge/FooterWithExactFeeCheck.tsxpackages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.tsxpackages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.test.tspackages/interwovenkit-react/src/pages/bridge/bridgePreviewFooterState.tspackages/interwovenkit-react/src/pages/bridge/data/preparation.tspackages/interwovenkit-react/src/pages/bridge/data/preparedRoute.test.tspackages/interwovenkit-react/src/pages/bridge/data/preparedRoute.tspackages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.tspackages/interwovenkit-react/src/pages/bridge/data/routeFreshness.tspackages/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.
Deploying interwovenkit-staging with
|
| 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 |
Deploying interwovenkit with
|
| Latest commit: |
a199134
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a7a575b5.interwovenkit.pages.dev |
| Branch Preview URL: | https://feat-bridge-preview-prewarm.interwovenkit.pages.dev |
Deploying interwovenkit-testnet with
|
| 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 |
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.
There was a problem hiding this comment.
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
getBridgeMsgsRequestKeyexport and its dead test coverage now that production code no longer references it.
- Removed the unused
- ✅ Fixed: Unnecessary factory wrapper always immediately invoked
- Simplified
getBridgeAddressListinto a direct async function and updated its only caller to await it without the redundant closure.
- Simplified
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.
There was a problem hiding this comment.
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 forroutefrom render. Whenrefetch()returns a newerlatestRoutehere 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.tsand await it forlatestRoutebefore the directnavigate().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
📒 Files selected for processing (3)
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsxpackages/interwovenkit-react/src/pages/bridge/data/routeFreshness.test.tspackages/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
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.
There was a problem hiding this comment.
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 | 🟠 MajorStability 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
📒 Files selected for processing (6)
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsxpackages/interwovenkit-react/src/pages/bridge/FooterWithMsgs.test.tspackages/interwovenkit-react/src/pages/bridge/data/balance.tspackages/interwovenkit-react/src/pages/bridge/data/messageRequestKey.tspackages/interwovenkit-react/src/pages/bridge/data/preparation.tspackages/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
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.
There was a problem hiding this comment.
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 | 🟠 MajorBlock repeat submits during preview preparation.
Lines 216-237 await
prefetchBridgeRoutePreparation(...), but the UI only disables onpreviewRefreshing, 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
📒 Files selected for processing (2)
packages/interwovenkit-react/src/pages/bridge/BridgeFields.tsxpackages/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.
There was a problem hiding this comment.
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.
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.
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.
Preview staleness should use the default quote window, not the shorter same-chain L1/L2 intervals used for BridgeFields simulation polling.
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.
There was a problem hiding this comment.
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.
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.
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.
|
Bugbot Autofix prepared fixes for both issues found in the latest run.
Or push these changes by commenting: 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.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
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.
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.


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
mainand reconciled exact fee check withuseChainEnabled/shouldCheckExactFee/shouldRunExactFeeQueryfrom upstream.Test plan
pnpm typecheckpnpm lint --max-warnings=0pnpm testpnpm prettier --check "**/*.{ts,tsx,json,css,md}"Summary by CodeRabbit
Bug Fixes
New Features
Refactor
Tests