Skip to content

♻️ app: prefer sendCalls, fix chain handling#879

Open
dieguezguille wants to merge 11 commits intotransportfrom
calls
Open

♻️ app: prefer sendCalls, fix chain handling#879
dieguezguille wants to merge 11 commits intotransportfrom
calls

Conversation

@dieguezguille
Copy link
Copy Markdown
Member

@dieguezguille dieguezguille commented Mar 12, 2026


Open with Devin

Summary by CodeRabbit

  • Bug Fixes

    • Fixed owner wallet detection, swap query invalidation, bridge fallback, forward chain handling, and pay simulation pending-state issues.
    • Resolved cross-chain/repay edge cases and transfer simulation errors.
  • Improvements

    • App-wide multi-chain awareness for data, simulations, and transaction routing.
    • Unified, more reliable send/swap execution with better error reporting and status tracking.
    • Streamlined swap flows with simplified success/pending/failure screens and added web padding.
  • Style

    • Adjusted repay amount selector font styling.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 12, 2026

🦋 Changeset detected

Latest commit: 80a1e20

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 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 chain-scoped context to many on-chain hooks, converts direct contract writes to mutation-driven sendCalls + waitForCallsStatus (paymaster integration), refactors Bridge to use source, restructures swap screens, and extends account client to accept optional chainId and perform chain switching.

Changes

Cohort / File(s) Summary
Changeset entries
​.changeset/bright-foxes-swim.md, ​.changeset/cool-icons-grow.md, ​.changeset/curly-pumas-jam.md, ​.changeset/cyan-flies-camp.md, ​.changeset/gold-cow-eat.md, ​.changeset/great-dryers-kick.md, ​.changeset/jolly-teeth-flow.md, ​.changeset/loose-papers-take.md, ​.changeset/open-beds-stand.md, ​.changeset/wide-cats-hug.md, ​.changeset/funny-aliens-mix.md, ​.changeset/humble-cities-drop.md
Add patch changeset metadata files for @exactly/mobile; no runtime code changes.
Bridge component
src/components/add-funds/Bridge.tsx
Replace effectiveSource with source across selection/queries/approval/transfer logic; add explicit error handling and move some transaction waits to waitForCallsStatus for batched submissions.
Mutation-driven on-chain flows
src/components/pay-mode/Pay.tsx, src/components/roll-debt/RollDebt.tsx, src/components/send-funds/Amount.tsx, src/components/swaps/Swaps.tsx
Replace direct writeContract calls with useMutation + encodeFunctionData + useSendCalls + waitForCallsStatus; integrate Alchemy/paymaster settings, consolidate send/propose paths and simulations.
Swap screens & UI
src/components/swaps/Failure.tsx, src/components/swaps/Pending.tsx, src/components/swaps/Success.tsx, src/components/swaps/Swaps.tsx
Simplify layouts to SafeView/ScrollView/YStack, centralize close/query invalidation, add external prop to Success, and route swap execution through new mutation/sendCalls path.
ChainId propagation across UI/hooks
src/components/.../*.tsx (card, home, loans, pay-mode, shared, etc.), src/utils/useAsset.ts, src/utils/usePendingOperations.ts, src/utils/usePortfolio.ts, src/utils/useSimulateProposal.ts, src/utils/usePendingOperations.ts
Add default chain import and pass chainId: chain.id into numerous hooks (useBytecode, useReadPreviewerExactly, installed-plugins, proposals, previewer snapshots) to scope queries by chain.
Account client / sendCalls handling
src/utils/accountClient.ts
Extend wallet_sendCalls payload to accept optional chainId, include chainId when calling sendCalls/getCallsStatus, add switchChain and encodeFunctionData handling to execute and recover from chain mismatches.
Misc UI & minor logic
src/components/pay-mode/RepayAmountSelector.tsx, src/utils/useAuth.ts, src/components/shared/InstallmentSelector.tsx, src/components/shared/PluginUpgrade.tsx, src/components/shared/Success.tsx, src/components/card/..., src/components/home/*, src/components/loans/*, src/utils/useSimulateProposal.ts
Style prop adjustments, SIWE connection caching, hook option updates to include chainId, small layout and translation additions, and re-exports/imports for chain-scoped behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Mobile Client
    participant Mutate as useMutation (swap/send)
    participant SendCalls as useSendCalls
    participant Paymaster as Alchemy Paymaster
    participant Chain as Blockchain Network
    participant Status as waitForCallsStatus

    Client->>Mutate: trigger swap/proposal (encoded calls + chainId)
    Mutate->>SendCalls: submit calls (paymaster policy, chainId)
    SendCalls->>Paymaster: request sponsored submission
    Paymaster->>Chain: submit transaction
    Chain->>Chain: execute calls
    Mutate->>Status: poll call status with chainId
    Status->>Chain: query receipt/status
    Chain->>Status: return status
    Status->>Mutate: resolve success/failure
    Mutate->>Client: return result
Loading
sequenceDiagram
    participant Component as UI Component
    participant ChainObj as chain (generated)
    participant Hook as Data Hook
    participant Query as On-chain Query Layer

    Component->>ChainObj: import chain
    Component->>Hook: call hook with { ..., chainId: chain.id }
    Hook->>Query: execute query scoped by chainId
    Query->>Hook: return chain-specific data
    Hook->>Component: provide scoped results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
  • franm91
  • nfmelendez
🚥 Pre-merge checks | ✅ 2
✅ 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 summarizes the main changes: refactoring to prefer sendCalls and fixing chain handling across the codebase.

✏️ 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 calls

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

❤️ Share

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

@cruzdanilo cruzdanilo changed the title ♻️ app: prefer sendCalls, fix chain handling ♻️ app: prefer sendCalls, fix chain handling Mar 12, 2026
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors core transaction logic to leverage sendCalls for a more unified and reliable approach to interacting with smart accounts. It introduces explicit chain ID handling across various components and hooks, resolving several issues related to cross-chain operations and transaction simulations. The changes aim to enhance the application's stability, particularly for complex DeFi interactions like bridging, repaying loans, and asset swaps, while also delivering minor UI refinements.

Highlights

  • Migration to sendCalls: Many transaction flows, including repay, rollover debt, and send funds, have been migrated to use the sendCalls mechanism, improving transaction handling and consistency.
  • Chain ID Handling: Explicit chainId parameters have been added to numerous wagmi hooks and transaction functions across the application to ensure correct chain context and prevent issues.
  • Bridge Functionality Enhancements: The bridge component now correctly forwards chain IDs, fixes simulation fallbacks, and includes robust error handling with waitForCallsStatus for bridge transactions.
  • Bug Fixes and UI Improvements: Several bugs were addressed, including owner wallet detection in SIWE authentication, swaps query invalidation, and UI layout adjustments for swap and repay screens.
Changelog
  • @exactly/mobile
    • 💄 restructure swap state screen layouts
    • 🐛 forward chain id in account client
    • 🐛 fix owner wallet detection in siwe auth
    • 🐛 fix swaps query invalidation
    • 🐛 fix bridge simulation falling back to default account
    • 🐛 pass chain id to bridge calls
    • 💄 fix repay amount selector font style
    • ♻️ migrate remaining flows to send calls
    • 💄 add bottom padding to swaps screen on web
    • ♻️ pass explicit chain id to read hooks
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 58.47953% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.44%. Comparing base (6acd9ae) to head (80a1e20).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/components/pay/Repay.tsx 58.82% 21 Missing ⚠️
src/utils/accountClient.ts 0.00% 17 Missing ⚠️
src/utils/reportError.ts 60.00% 16 Missing ⚠️
src/components/send-funds/Amount.tsx 73.07% 7 Missing ⚠️
...rc/components/home/card-upgrade/UpgradeAccount.tsx 0.00% 4 Missing ⚠️
src/components/loans/Review.tsx 33.33% 2 Missing ⚠️
src/components/roll-debt/RollDebt.tsx 88.23% 2 Missing ⚠️
src/utils/useAuth.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           transport     #879      +/-   ##
=============================================
- Coverage      71.67%   71.44%   -0.23%     
=============================================
  Files            228      228              
  Lines           8283     8315      +32     
  Branches        2662     2671       +9     
=============================================
+ Hits            5937     5941       +4     
- Misses          2116     2145      +29     
+ Partials         230      229       -1     
Flag Coverage Δ
e2e 52.21% <58.47%> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +165 to 173
const { calls, chainId, from, id } = params[0] as {
calls: readonly Call[];
chainId?: Hex;
from?: Address;
id?: string;
};
if (from && from !== accountAddress) throw new Error("bad account");
if (queryClient.getQueryData<AuthMethod>(["method"]) === "webauthn") {
const { hash } = await client.sendUserOperation({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 webauthn path in accountClient ignores chainId parameter — safe for current callers

The wallet_sendCalls handler at src/utils/accountClient.ts:165 now extracts chainId from params but the webauthn path (lines 172-176) ignores it entirely, always sending user operations on the default chain and encoding chain.id in the response. This is currently safe because all callers that go through the smart account config (exa) don't pass explicit chainId — the bridge uses ownerConfig directly. However, if a future caller passes a non-default chainId through the exa config with webauthn auth, the transaction would silently execute on the wrong chain.

(Refers to lines 165-176)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

gemini-code-assist[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@dieguezguille dieguezguille force-pushed the calls branch 2 times, most recently from 991a102 to 80f63cc Compare March 12, 2026 16:29
sentry[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/pay-mode/Pay.tsx (1)

473-501: ⚠️ Potential issue | 🟠 Major

Add chainId: chain.id to external asset mutateSendCalls invocation.

Same issue as the other mutation: the external repay path should also specify the chain to prevent cross-chain mismatches.

🔧 Proposed fix
       const { id } = await mutateSendCalls({
+        chainId: chain.id,
         calls: [
           {
             to: selectedAsset.address,
             abi: erc20Abi,
             functionName: "approve",
             args: [swapperAddress, route.fromAmount],
           },
           // ... rest of calls
         ],
         capabilities: {
           paymasterService: {
             url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
             context: { policyId: alchemyGasPolicyId },
           },
         },
       });
♻️ Duplicate comments (6)
.changeset/open-beds-stand.md (1)

1-5: ⚠️ Potential issue | 🟠 Major

Rewrite or drop this stale changeset entry.

This note still says “add bottom padding to swaps screen on web”, but this PR is about sendCalls and chain handling. If the change is internal, this should be an empty changeset; otherwise, rewrite it to the actual user-visible fix.

Based on learnings, empty changesets are required for non-user-facing changes, and changeset summaries in .changeset/*.md must start with a gitmoji and follow the format <emoji> <message> (no scope).

src/components/shared/PluginUpgrade.tsx (1)

33-46: ⚠️ Potential issue | 🔴 Critical

Pin the upgrade write path to chain.id as well.

These reads are now scoped to chain.id, but the batch submission and status polling below still default to the active wallet chain. If the wallet is on another network, the upgrade can be submitted or polled against a different chain than the one you just simulated.

🔧 Minimal fix
       const { id } = await mutateSendCalls({
+        chainId: chain.id,
         calls: [
           { ...uninstallPluginSimulation.request, to: address },
           {
             to: address,
@@
         capabilities: {
           paymasterService: {
             url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
             context: { policyId: alchemyGasPolicyId },
           },
         },
       });
-      const { status } = await waitForCallsStatus(exa, { id });
+      const { status } = await waitForCallsStatus(exa, { id, chainId: chain.id });
In wagmi, do useSendCalls/sendCalls mutation variables and waitForCallsStatus accept a chainId parameter, and should it be passed when reads/simulations are already pinned to a specific chain?

Also applies to: 59-85

src/components/swaps/Swaps.tsx (1)

339-380: ⚠️ Potential issue | 🔴 Critical

Pin the swap submission and status polling to chain.id.

The simulations above are scoped to chain.id, but this batch write path still uses the wallet’s active chain by default. That can send the swap on a different network than the one you just simulated, or poll the wrong chain for status.

🔧 Minimal fix
       const { id } = await mutateSendCalls({
+        chainId: chain.id,
         calls: [call],
         capabilities: {
           paymasterService: {
             url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
             context: { policyId: alchemyGasPolicyId },
           },
         },
       });
-      const { status } = await waitForCallsStatus(exaConfig, { id });
+      const { status } = await waitForCallsStatus(exaConfig, { id, chainId: chain.id });
In wagmi, do useSendCalls/sendCalls mutation variables and waitForCallsStatus accept a chainId parameter, and should it be passed when reads/simulations are already pinned to a specific chain?
src/components/roll-debt/RollDebt.tsx (1)

290-300: ⚠️ Potential issue | 🟠 Major

Add chainId: chain.id to the mutateSendCalls invocation.

The mutation uses chain-scoped simulation and polling, but mutateSendCalls is called without an explicit chainId. This can cause the transaction to be submitted on the wallet's currently connected chain rather than the intended chain. The past review comment flagged this issue as addressed, but the fix appears to be missing.

🔧 Proposed fix
       const { id } = await mutateSendCalls({
+        chainId: chain.id,
         calls: [{ to, data: encodeFunctionData({ abi, functionName, args }) }],
         capabilities: {
           paymasterService: {
             url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
             context: { policyId: alchemyGasPolicyId },
           },
         },
       });
src/components/send-funds/Amount.tsx (1)

165-178: ⚠️ Potential issue | 🟠 Major

Add chainId: chain.id to the mutateSendCalls invocation.

All simulations in this component are chain-scoped, but mutateSendCalls is called without an explicit chainId. This can result in the transaction being submitted on the wallet's currently connected chain instead of the intended chain. The subsequent waitForCallsStatus call uses the single-chain exa config, which may fail to poll status correctly if chains mismatch.

🔧 Proposed fix
   const sendCalls = async (calls: readonly { data?: `0x${string}`; to: `0x${string}`; value?: bigint }[]) => {
     const { id } = await mutateSendCalls({
+      chainId: chain.id,
       calls,
       capabilities: {
         paymasterService: {
           url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
           context: { policyId: alchemyGasPolicyId },
         },
       },
     });
src/components/pay-mode/Pay.tsx (1)

427-435: ⚠️ Potential issue | 🟠 Major

Add chainId: chain.id to mutateSendCalls invocation.

All reads and simulations in this component explicitly pin to chain.id, but this mutation executes calls without specifying the chain. A wallet connected to a different network could submit the transaction on the wrong chain while waitForCallsStatus polls on exa's configured chain.

🔧 Proposed fix
       const { id } = await mutateSendCalls({
+        chainId: chain.id,
         calls: [call],
         capabilities: {
           paymasterService: {
             url: `${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`,
             context: { policyId: alchemyGasPolicyId },
           },
         },
       });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f49faff4-bf2f-4d46-a9e9-1b0dacece46f

📥 Commits

Reviewing files that changed from the base of the PR and between 80f63cc and 70e9366.

📒 Files selected for processing (48)
  • .changeset/bright-foxes-swim.md
  • .changeset/cool-icons-grow.md
  • .changeset/curly-pumas-jam.md
  • .changeset/cyan-flies-camp.md
  • .changeset/funny-aliens-mix.md
  • .changeset/gold-cow-eat.md
  • .changeset/great-dryers-kick.md
  • .changeset/jolly-teeth-flow.md
  • .changeset/loose-papers-take.md
  • .changeset/open-beds-stand.md
  • .changeset/wide-cats-hug.md
  • src/components/add-funds/Bridge.tsx
  • src/components/card/Card.tsx
  • src/components/card/exa-card/CardContents.tsx
  • src/components/defi/DeFi.tsx
  • src/components/getting-started/GettingStarted.tsx
  • src/components/home/AssetList.tsx
  • src/components/home/CardLimits.tsx
  • src/components/home/Home.tsx
  • src/components/home/HomeActions.tsx
  • src/components/home/Portfolio.tsx
  • src/components/home/card-upgrade/UpgradeAccount.tsx
  • src/components/loans/Amount.tsx
  • src/components/loans/Asset.tsx
  • src/components/loans/CreditLine.tsx
  • src/components/loans/LoanSummary.tsx
  • src/components/loans/Loans.tsx
  • src/components/loans/Review.tsx
  • src/components/pay-mode/OverduePayments.tsx
  • src/components/pay-mode/Pay.tsx
  • src/components/pay-mode/PayMode.tsx
  • src/components/pay-mode/PaySelector.tsx
  • src/components/pay-mode/RepayAmountSelector.tsx
  • src/components/pay-mode/UpcomingPayments.tsx
  • src/components/roll-debt/RollDebt.tsx
  • src/components/send-funds/Amount.tsx
  • src/components/shared/InstallmentSelector.tsx
  • src/components/shared/PluginUpgrade.tsx
  • src/components/swaps/Failure.tsx
  • src/components/swaps/Pending.tsx
  • src/components/swaps/Success.tsx
  • src/components/swaps/Swaps.tsx
  • src/utils/accountClient.ts
  • src/utils/useAsset.ts
  • src/utils/useAuth.ts
  • src/utils/usePendingOperations.ts
  • src/utils/usePortfolio.ts
  • src/utils/useSimulateProposal.ts

Comment on lines +1 to +5
---
"@exactly/mobile": patch
---

♻️ migrate remaining flows to send calls
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make this changeset empty unless the refactor is user-visible.

migrate remaining flows to send calls reads like an internal implementation detail, not a release note. If this is only transaction-plumbing work, this should be an empty changeset; otherwise, rewrite it in user-facing terms. Based on learnings, empty changesets are required in this repo for non-user-facing changes that do not warrant release notes.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20f2f6d3e7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +165 to +167
const { calls, chainId, from, id } = params[0] as {
calls: readonly Call[];
chainId?: Hex;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Respect requested chain in webauthn sendCalls

wallet_sendCalls now accepts a chainId, but this code path does not apply it for webauthn: it always sends via the default chain client and returns an id encoded with chain.id. If a caller requests a different network, webauthn users will submit on the wrong chain (or poll status against the wrong chain metadata), which breaks chain-aware send flows. Please either reject non-default chainId for webauthn explicitly or execute against a client configured for the requested chain.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@coderabbitai are we passing the chain id in the webauthn flow in the latest force-pushed code?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@dieguezguille — looking at the current file content, the chain ID is partially handled in the webauthn flow:

  • requestedChainId is now correctly computed from the incoming chainId param (or falls back to chain.id).
  • It is encoded into the returned ID:
    return { id: concat([hash, numberToHex(requestedChainId, { size: 32 }), UO_MAGIC_ID]) };
    This means wallet_getCallsStatus will read back the correct chain from the ID — addressing the status-polling concern.

However, the actual executionclient.sendUserOperation(...) — still uses the client instance that was constructed at startup with the default chain. There is no chain-switching or alternate bundler client being used if a different chainId is requested.

So to summarize:

  • ✅ The returned ID now encodes requestedChainId (status polling is correct).
  • ❌ The user operation is still submitted via the bundler client bound to the default chain, regardless of what chainId was requested.

If in your app the webauthn flow is only ever called with the default chain, this is fine in practice. But if cross-chain calls are possible for webauthn users, the execution still won't target the requested chain.


🧠 Learnings used
Learnt from: cruzdanilo
Repo: exactly/exa PR: 633
File: src/utils/accountClient.ts:188-203
Timestamp: 2026-01-12T20:25:48.408Z
Learning: In src/utils/accountClient.ts, the wallet_getCallsStatus handler explicitly checks for UO_MAGIC_ID to handle user operation receipts, while TX_MAGIC_ID falls through to wagmi's getCallsStatus, which automatically handles the custom ID format. This is intentional—viem/wagmi natively supports TX_MAGIC_ID, and the UO_MAGIC_ID scheme was inspired by this pattern. When reviewing changes or extending support for additional magic IDs, ensure new IDs are compatible with wagmi/viem expectations and do not inadvertently bypass intended handling. This guidance is specific to this file (and related ID handling patterns) and should not be generalized beyond this context.

Learnt from: cruzdanilo
Repo: exactly/exa PR: 669
File: src/utils/queryClient.ts:55-63
Timestamp: 2026-01-22T21:34:44.852Z
Learning: When a function returns Promisable<T> (defined as T | PromiseLike<T>), wrap the returned value with Promise.resolve() to ensure the function always returns a proper Promise<T>. This provides a consistent asynchronous API and avoids mixed sync/async return types. Apply this pattern to TypeScript code handling Promisable<T>, including files like src/utils/queryClient.ts and other TS files in the repository.

Learnt from: itofarina
Repo: exactly/exa PR: 807
File: contracts/script/Redeployer.s.sol:101-101
Timestamp: 2026-02-19T16:02:22.395Z
Learning: Trailing commas should be used in TypeScript and JavaScript files (extensions .ts, .tsx, .js, .jsx) to improve diffs and reduce merge conflicts. Do NOT use trailing commas in Solidity (*.sol) or standard JSON (*.json) files, as they are syntax errors in those languages. Apply this guidance across the repository for files matching the listed extensions; ensure tooling (linters/formatters) are configured accordingly to enforce the rule.

Learnt from: mainqueg
Repo: exactly/exa PR: 896
File: server/hooks/bridge.ts:115-115
Timestamp: 2026-03-20T13:41:53.508Z
Learning: In this TypeScript codebase (with `noUncheckedIndexedAccess` enabled), array index access like `arr[0]` is typed as `T | undefined`. A preceding length guard such as `arr.length === 1` does not narrow the specific index access to non-undefined. Therefore, do not recommend removing optional chaining (e.g., `arr[0]?.prop`) based solely on a preceding `.length` check; keep `?.` when the index access type includes `undefined`.

Learnt from: cruzdanilo
Repo: exactly/exa PR: 914
File: src/utils/useSimulateProposal.ts:82-94
Timestamp: 2026-03-25T23:18:25.761Z
Learning: In exactly/exa, `viem`’s exported `multicall3Abi` is only the subset of the Multicall3 contract ABI covering aggregation functions (e.g., `aggregate3`), and it does not include utility/view functions such as `getBlockNumber` (and likely not `getCurrentBlockTimestamp`). When using `useReadContracts` to call Multicall3 utility functions (e.g., `getBlockNumber`, `getCurrentBlockTimestamp`), provide an inline ABI for those specific function(s) instead of reusing `multicall3Abi`. Do not suggest replacing inline ABIs with `multicall3Abi` for Multicall3 utility/view calls.

coderabbitai[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1625103ae8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

},
onSuccess() {
queryClient.invalidateQueries({ queryKey: ["lifi", "tokenBalances"] }).catch(reportError);
queryClient.invalidateQueries({ queryKey: marketsQueryKey }).catch(reportError);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate only markets queries after a swap

marketsQueryKey is read from useMarkets(), but that hook does not define a dedicated markets key in its return value, so this value can be undefined; passing undefined to invalidateQueries broadens the filter and can invalidate/refetch the entire cache after each successful swap. That adds avoidable network churn and UI refreshes in unrelated screens whenever a swap succeeds. Use an explicit markets query key (or expose one from useMarkets) before calling invalidateQueries here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 0 new potential issues.

View 17 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b296cd1f1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

},
},
});
const { status } = await waitForCallsStatus(exaConfig, { id });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle tx-fallback IDs before waiting call status

This path now hard-requires waitForCallsStatus, but the account client can still fall back from wallet_sendCalls to a plain sendTransaction when the owner wallet lacks 5792 support. In that fallback, the returned id is not reliably resolvable by wallet_getCallsStatus, so this await can throw even after the transaction was actually sent/mined, turning successful swaps into failures for those wallets. Please add a fallback receipt path (or ensure fallback ids are fully supported by status polling) before treating this as a hard failure.

Useful? React with 👍 / 👎.

Comment on lines +379 to +380
const amount = withUSDC ? repayAssets : route?.fromAmount;
if (!amount) throw new Error("no route");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid requiring route data for legacy cross-repay

The new guard enforces route?.fromAmount for every non-USDC repayment mode, but legacyCrossRepay submits legacyCrossRepaySimulation.request and does not consume route payload in its call construction. If LI.FI quoting is unavailable, users on the legacy plugin can no longer repay even when on-chain simulation is ready. This introduces an unnecessary external dependency in a flow that previously could proceed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 17 additional findings in Devin Review.

Open in Devin Review

Comment on lines +166 to +169
function walkCause(error: unknown): unknown {
if (typeof error === "object" && error !== null && "cause" in error && error.cause !== undefined)
return walkCause(error.cause);
return error;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 walkCause traverses to deepest error cause, changing which properties are examined

The new walkCause function at src/utils/reportError.ts:166-169 recursively walks the .cause chain to the deepest error. This changes parseError to extract code, status, name, and message from the innermost cause rather than the top-level error. For standard wallet rejection flows (MetaMask, injected wallets), the deepest cause is typically the raw RPC error object { code: 4001, message: "..." }, so walletRejected (status === "4001") is correctly detected. However, for some wallet connectors where the deepest cause is a plain Error("User rejected...") without a code property, walletRejected would be false. This matters critically in src/utils/accountClient.ts:194 where classifyError(error).authKnown gates whether a rejection is re-thrown vs. falling through to the sendTransaction fallback. The owner config at src/utils/wagmi/owner.ts:15 uses injected() and farcasterMiniApp() connectors (no WalletConnect), so the risk is limited to those specific connector error structures. Note also that revert and reason at lines 101-102 still use the original error, not root, creating an intentional asymmetry.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80a1e20ca0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +126 to +127
const result = await waitForCallsStatus(exa, { id });
if (result.status === "failure") throw new Error("failed to send");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add tx fallback before waiting send-funds call status

This new send path hard-requires waitForCallsStatus, but the SIWE account client can still fall back from wallet_sendCalls to sendTransaction when 5792 is unavailable. In that fallback case the returned id is tx-based (not reliably resolvable by wallet_getCallsStatus), so this await can throw even after the transfer is actually mined, incorrectly surfacing a failed send. Please add a receipt/hash fallback path (or status handling for tx-style ids) before treating this as a hard failure.

Useful? React with 👍 / 👎.

Comment on lines +146 to +149
tokenSelectionType === "to"
? selected.address === fromToken.token.address
? parseUnits(formatUnits(old.fromAmount, fromToken.token.decimals), toToken.token.decimals)
: old.fromAmount
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use current quoted amount when swapping tokens from to-picker

This branch converts old.fromAmount, but after the refactor that stopped syncing route results back into swap state, old.fromAmount can be stale whenever activeInput === "to" (the displayed source amount is now route.fromAmount). If the user swaps sides by selecting the current source token in the destination picker, this can overwrite the live quoted amount with an outdated value. Use the effective current source amount instead of persisted old.fromAmount in this path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 18 additional findings in Devin Review.

Open in Devin Review

Comment on lines 65 to 69
dropError: (error) => {
if (error instanceof ValiError) return true;
if (error instanceof UserRejectedRequestError) return true;
const { passkeyCancelled, passkeyNotAllowed } = classifyError(error);
return passkeyCancelled || passkeyNotAllowed;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Auth query dropError no longer drops wallet rejection errors for SIWE flow

The dropError handler for the ["auth"] query in server.ts:65-69 removed the error instanceof UserRejectedRequestError check but only checks passkeyCancelled || passkeyNotAllowed, which are passkey-specific classifications. In the SIWE auth flow, when a user rejects a wallet signature, viem throws a UserRejectedRequestError. The new classifyError correctly identifies this as walletRejected (via status === "4001") and authKnown, but dropError doesn't check those properties. As a result, the wallet rejection error is NOT dropped — it flows through the QueryCache.onError at src/utils/queryClient.ts:44-56 and is reported to Sentry (as a warning-level event since knownWarning includes walletRejected). Previously this was silently dropped. The dropError should also return true for authKnown errors (or at least walletRejected).

Suggested change
dropError: (error) => {
if (error instanceof ValiError) return true;
if (error instanceof UserRejectedRequestError) return true;
const { passkeyCancelled, passkeyNotAllowed } = classifyError(error);
return passkeyCancelled || passkeyNotAllowed;
},
dropError: (error) => {
if (error instanceof ValiError) return true;
const { passkeyCancelled, passkeyNotAllowed, authKnown } = classifyError(error);
return passkeyCancelled || passkeyNotAllowed || authKnown;
},
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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