Skip to content

Viem Migration - Connection issues when open in diff tabs#7441

Closed
brunota20 wants to merge 49 commits intorelease/28-04-2026from
bruno/cow-925-connection-issues-when-the-app-is-opened-in-safe-and-in-a
Closed

Viem Migration - Connection issues when open in diff tabs#7441
brunota20 wants to merge 49 commits intorelease/28-04-2026from
bruno/cow-925-connection-issues-when-the-app-is-opened-in-safe-and-in-a

Conversation

@brunota20
Copy link
Copy Markdown
Collaborator

@brunota20 brunota20 commented Apr 30, 2026

Summary

Fixes https://www.notion.so/cownation/Connection-issues-when-the-app-is-opened-in-Safe-and-in-a-regular-tab-3518da5f04ca8056a0bae093b19f3538
Connection issues when the app is opened in Safe and in a regular tab. We could not connect any wallet on the regular tab when the safe tab was opened.

To Test

  1. <> Open the safe app
  2. <> Open the a regular cow swap tab
  • <<What to expect?>> We should be able to connect any wallet on the regular tab while the safe app is opened on another tab.
Screenshot 2026-04-30 at 18 36 27

https://www.loom.com/share/0b505107a5374579ac15311ea9a699c3

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 30, 2026

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

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview May 7, 2026 2:11pm
explorer-dev Ready Ready Preview May 7, 2026 2:11pm
storybook Error Error May 7, 2026 2:11pm
storybook-dev Error Error May 7, 2026 2:11pm
swap-dev Ready Ready Preview May 7, 2026 2:11pm
widget-configurator Ready Ready Preview May 7, 2026 2:11pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored May 7, 2026 2:11pm
sdk-tools Ignored Ignored Preview May 7, 2026 2:11pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c6318957-b07a-4036-961f-8ad6ec06507f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The change modifies iframe-specific storage handling in the Wagmi configuration by monkey-patching localStorage to route AppKit-prefixed keys to sessionStorage when embedded. Additionally, a separate storage key is used for iframe contexts to prevent wallet state sharing between iframe and top-level tabs.

Changes

Cohort / File(s) Summary
Iframe Storage Isolation
libs/wallet/src/wagmi/config.ts
Implements storage isolation for iframe contexts by monkey-patching localStorage to route @appkit/… prefixed keys to sessionStorage, while preserving original behavior for other keys. Updates Wagmi storage key selection to use cowswap-wallet-safe in iframe contexts instead of shared storage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 In iframes small, we hop with care,
Storage split, no secrets shared,
AppKit keys take the session route,
While others keep their persistent route! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title references 'Viem Migration' and 'connection issues when open in diff tabs', which partially aligns with the changeset's focus on fixing connection issues across different contexts (Safe tab vs regular tab), but 'Viem Migration' is misleading as the changes are about iframe storage isolation and context-specific localStorage keys, not a Viem migration. Update the title to focus on the actual change: something like 'Fix connection issues by using context-specific storage keys' would better reflect the iframe storage isolation implementation.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes a summary with issue link, test steps with checkboxes, and a supporting screenshot/video, mostly following the template structure.

✏️ 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 bruno/cow-925-connection-issues-when-the-app-is-opened-in-safe-and-in-a

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/wallet/src/wagmi/config.ts`:
- Around line 55-80: Before overriding localStorage for iframe behavior, run a
one-time purge of any existing legacy keys that start with '@appkit/': iterate
through current localStorage keys and remove (or migrate to sessionStorage)
entries whose keys startWith('@appkit/'), then proceed to bind
origSetItem/origGetItem/origRemoveItem and install the iframe redirect logic in
the same block (refer to isEmbeddedInIframe, origSetItem, origGetItem,
origRemoveItem, and the localStorage.setItem/getItem/removeItem overrides).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 74f8fbea-9b98-44fa-8a1b-e22c71896761

📥 Commits

Reviewing files that changed from the base of the PR and between 95095f2 and d7e17fb.

📒 Files selected for processing (1)
  • libs/wallet/src/wagmi/config.ts

Comment thread libs/wallet/src/wagmi/config.ts Outdated
Comment on lines +55 to +80
if (typeof window !== 'undefined' && isEmbeddedInIframe()) {
const origSetItem = localStorage.setItem.bind(localStorage)
const origGetItem = localStorage.getItem.bind(localStorage)
const origRemoveItem = localStorage.removeItem.bind(localStorage)

localStorage.setItem = (key: string, value: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.setItem(key, value)
} else {
origSetItem(key, value)
}
}
localStorage.getItem = (key: string): string | null => {
if (key.startsWith('@appkit/')) {
return sessionStorage.getItem(key)
}
return origGetItem(key)
}
localStorage.removeItem = (key: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.removeItem(key)
} else {
origRemoveItem(key)
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Purge legacy @appkit/* state before installing the iframe redirect.

This only stops new iframe writes. Any @appkit/* entries older builds already left in shared localStorage will still be read by the regular tab after upgrade, so affected users can keep hitting the stale reconnect state until they clear storage manually.

Suggested one-time cleanup
 if (typeof window !== 'undefined' && isEmbeddedInIframe()) {
+  for (let i = localStorage.length - 1; i >= 0; i -= 1) {
+    const key = localStorage.key(i)
+
+    if (key?.startsWith('@appkit/')) {
+      localStorage.removeItem(key)
+    }
+  }
+
   const origSetItem = localStorage.setItem.bind(localStorage)
   const origGetItem = localStorage.getItem.bind(localStorage)
   const origRemoveItem = localStorage.removeItem.bind(localStorage)
📝 Committable suggestion

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

Suggested change
if (typeof window !== 'undefined' && isEmbeddedInIframe()) {
const origSetItem = localStorage.setItem.bind(localStorage)
const origGetItem = localStorage.getItem.bind(localStorage)
const origRemoveItem = localStorage.removeItem.bind(localStorage)
localStorage.setItem = (key: string, value: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.setItem(key, value)
} else {
origSetItem(key, value)
}
}
localStorage.getItem = (key: string): string | null => {
if (key.startsWith('@appkit/')) {
return sessionStorage.getItem(key)
}
return origGetItem(key)
}
localStorage.removeItem = (key: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.removeItem(key)
} else {
origRemoveItem(key)
}
}
}
if (typeof window !== 'undefined' && isEmbeddedInIframe()) {
for (let i = localStorage.length - 1; i >= 0; i -= 1) {
const key = localStorage.key(i)
if (key?.startsWith('@appkit/')) {
localStorage.removeItem(key)
}
}
const origSetItem = localStorage.setItem.bind(localStorage)
const origGetItem = localStorage.getItem.bind(localStorage)
const origRemoveItem = localStorage.removeItem.bind(localStorage)
localStorage.setItem = (key: string, value: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.setItem(key, value)
} else {
origSetItem(key, value)
}
}
localStorage.getItem = (key: string): string | null => {
if (key.startsWith('@appkit/')) {
return sessionStorage.getItem(key)
}
return origGetItem(key)
}
localStorage.removeItem = (key: string) => {
if (key.startsWith('@appkit/')) {
sessionStorage.removeItem(key)
} else {
origRemoveItem(key)
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/wallet/src/wagmi/config.ts` around lines 55 - 80, Before overriding
localStorage for iframe behavior, run a one-time purge of any existing legacy
keys that start with '@appkit/': iterate through current localStorage keys and
remove (or migrate to sessionStorage) entries whose keys startWith('@appkit/'),
then proceed to bind origSetItem/origGetItem/origRemoveItem and install the
iframe redirect logic in the same block (refer to isEmbeddedInIframe,
origSetItem, origGetItem, origRemoveItem, and the
localStorage.setItem/getItem/removeItem overrides).

@lgahdl lgahdl changed the base branch from develop to release/28-04-2026 April 30, 2026 23:22
@lgahdl
Copy link
Copy Markdown
Collaborator

lgahdl commented Apr 30, 2026

The problem is still happening, steps to reproduce:

1 - Open the vercel link: https://swap-dev-git-bruno-cow-925-connection-issues-5aeb03-cowswap-dev.vercel.app/#/8453/swap/WETH/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, connect with metamask, it will work fine
2 - Disconnect Metamask
3 - Open the app embedded in a safe: https://app.safe.global/apps/open?safe=avax:0xbed38A0c063732E9fa5baF419e46C5E8afFdAf5A&appUrl=https%3A%2F%2Fswap-dev-git-bruno-cow-925-connection-issues-5aeb03-cowswap-dev.vercel.app/
4 - Connect your safe, metamask wallet being safe's signer or not, metamask will disconnect always, rabby will disconnect if you change the tab.
5 - Open the vercel link again (not embedded), try to connect metamask again, it will disconnect automatically on every attempt

* chore: update sdk and simplify deps

* chore(i18n): extract i18n strings [automatic]

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Denis Makarov <limitofzero@gmail.com>
@brunota20 brunota20 changed the title Bruno/cow 925 connection issues when the app is opened in safe and in a Viem Migration - Connection issues when open in diff tabs May 1, 2026
@brunota20 brunota20 closed this May 1, 2026
@brunota20 brunota20 force-pushed the bruno/cow-925-connection-issues-when-the-app-is-opened-in-safe-and-in-a branch from 712866e to 95095f2 Compare May 1, 2026 20:03
@github-actions github-actions Bot locked and limited conversation to collaborators May 1, 2026
brunota20 and others added 2 commits May 1, 2026 17:09
…allet_revokePermissions

When SafeConnectionHandler switched from the injected connector to the safe
connector inside a Safe App iframe, it called wagmi's disconnect() which
internally calls wallet_revokePermissions on MetaMask. Since MetaMask manages
permissions per origin (not per tab), this revoked the eth_accounts permission
for the entire localhost:3000 origin, causing any open browser tab at that
URL to lose its wallet connection.

Fix: write the shimDisconnect storage flag directly instead of calling
disconnect(). This prevents the connector from being reconnected on next load
without touching MetaMask's global permissions. The connectSafeInIframe effect
still runs and makes the safe connector the active connection.

Also introduce IS_CROSS_ORIGIN_IFRAME — a one-time check that distinguishes
genuine cross-origin iframes (Safe App) from same-origin ones (browser
extensions like Loom or 1Password that also run inside iframes). This ensures
SafeConnectionHandler only activates in actual cross-origin contexts.
@lgahdl lgahdl reopened this May 1, 2026
@lgahdl
Copy link
Copy Markdown
Collaborator

lgahdl commented May 1, 2026

Worked here!

lgahdl and others added 5 commits May 5, 2026 11:49
AppKit was being initialised inside the injected widget (cross-origin
iframe), causing localStorage key conflicts with the host app — connecting
in the widget would auto-connect the standalone app, and disconnecting the
app would disconnect the widget.

Skip AppKit for all cross-origin iframes (both Safe App and injected widget)
instead of only the Safe iframe. Each context already gets a distinct wagmi
storage key, so session state remains isolated. The addWagmiConnector modal
patch is no longer needed since AppKit doesn't run in the widget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or widget

Resolve merge conflicts in config.ts — keep widget AppKit with Storage.prototype
interception to isolate @appkit/* keys, conditional enableReconnect, and namespaced
wagmi storage keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dget

- Stop clearing @appkit/* keys on widget init — this was wiping the main
  app's AppKit state, causing two-step disconnect and stale Account view.
- Remove plain injected connector from widget — MetaMask is a per-origin
  singleton, registering it leaked wallet state between widget and app.
- Disable EIP-6963 in widget to prevent auto-detecting the main app's
  injected wallet.
- Keep Storage.prototype interception to redirect widget's @appkit/*
  operations to sessionStorage, preventing cross-tab storage events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ow-925-connection-issues-when-the-app-is-opened-in-safe-and-in-a
@brunota20
Copy link
Copy Markdown
Collaborator Author

https://www.loom.com/share/48f00f73a0ba47a9bd5f81ee28e55cf8

I tested using one safe tab, one regular tab and one widget tab opened, connecting and disconnecting to check the behavior

…925-connection-issues-when-the-app-is-opened-in-safe-and-in-a"

This reverts commit c7ffaef, reversing
changes made to 36292eb.
@yvesfracari
Copy link
Copy Markdown
Collaborator

We just more tests and the issue is still happening between widget and the app. This issue is being harder because of the new package (appKit) underlying differences from the previous ones. We'll keep you updated once is solved.

@brunota20
Copy link
Copy Markdown
Collaborator Author

After a few attempts of fixing the widget standalone mode connection, we are still facing the issue when using wallets like metamask or rabby (extensions), since they are using the same window.ethereum on both tabs. I am still investigating on how to do this using appkit.

Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

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

Hey @brunota20 ,
Interaction safe app-regular app works great.

Interaction widget-regular app is still not great. Besides, there are many regressions in the widget itself:

  • I'm connected to the app to MM, and I cannot connect to standalone more: it shows 'connection declined' error (this also presents on your video)
Image
  • widget: when I'm connected to the standalone mode, I switch to the dapp mode --> it appears that I'm connected, but the side panel shows an opposite state
Image
  • widget: I'm connected to both modes. When I disconnect the wallet from the standalone mode, navigate to the dapp mode, I see that the widget shows 'connect wallet' state, but the side panel looks like connected.
Image
  • widget: I see an error in the console: could you please check what it means?
Image

This is the video how it works now on Prod https://www.loom.com/share/a50e2d2a7d4742108d40e4f56c450967

And this is what I expect to see in the PR:

  • I should be able to connect to 3 different wallets in different chains in 3 modes
  • connection is not reset when I change it in one of modes (note: in the video connection is reset with MM because their rate limiting issue)
  • connection is not reset in any of modes when I connect to a different wallet in another app
  • when I'm signing an order from any of modes, it calls the correct wallet to connect

@socket-security
Copy link
Copy Markdown

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants