Skip to content

feat(wallet): use reown for wallets management#7639

Open
shoom3301 wants to merge 14 commits into
mainfrom
fix/wallets
Open

feat(wallet): use reown for wallets management#7639
shoom3301 wants to merge 14 commits into
mainfrom
fix/wallets

Conversation

@shoom3301

@shoom3301 shoom3301 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

After the hotfix, we realized that Safe app crashes. Debugging showed that it comes from SafeConnectionHandler.
While debugging I also realized that we have lots of redundant code and we don't utilize reown as expected.

Important

  1. It's important to set reconnectOnMount={false} to <WagmiProvider>, because Reown handles the reconnect
  2. wagmiStorage should have storage: localStorage to make the reconnect correct. Before, it was sessionStorage for multi-tab wallet isolation, but it didn't really work
  3. WAGMI_STORAGE_KEY covers isolation between CoW Swap/Widget/Safe
  4. getIsSafeAppIframe() helps us synchronously understand that the app is open in Safe, so we can configure the connector and initiate its connection

What was deleted (the old machinery)

  • wagmi/SafeConnectionHandler.tsx (148 lines) — manual Safe-app connection handling
  • wagmi/providerIsolation.ts + its test (314 lines) — EIP-6963 interception and per-tab provider wrapping
  • wagmi/initialReconnectLifecycle.ts — the "pending/settled" reconnect-lifecycle store
  • reown/init.ts — old AppKit init entry point
  • The app-side injectedWidget/updaters/WidgetStandaloneMode.updater.tsx (moved into the wallet lib)
  • Massive ReconnectOnMount / reconnectWidgetConnector logic and the config.setState invariant guard in Web3Provider.tsx and config.ts

What was added / changed

  • wagmi/config.ts — rewritten from ~360 lines to ~190. Now builds a single WagmiAdapter + createAppKit, drops the IS_CROSS_ORIGIN_IFRAME / sessionStorage-patching / EIP-6963 interception code.
    Auto-connects to Safe via connectWalletById(SAFE_CONNECTOR_ID) when in a Safe iframe. Exports wagmiAdapter, reownAppKit, wagmiStorage.
  • wagmi/getConnectors.ts (new) — small factory returning the Safe connector (in Safe iframe) and/or the CoW Widget injected connector (in widget). Plain wallets are left to AppKit/EIP-6963.
  • wagmiStorage.ts (new) — context-scoped wagmi storage key (cowswap-wallet[-cow-widget][_safe-app]) backed by localStorage.
  • updaters/WidgetStandaloneMode.updater.tsx (new, in wallet lib) — now takes a standaloneMode prop and keeps the connection consistent with dapp vs. standalone mode (connect/hide the cow-widget connector, disconnect disallowed connectors). Ships with a 236-line test file.
  • utils/connectWalletById.ts & utils/getIsSafeAppIframe.ts (new) — small shared helpers, now exported from the lib index.
  • hooks/useDisconnectWallet.ts — simplified to just call reownAppKit.disconnect() (dropped the USER_DISCONNECTED_SESSION_KEY workaround).
  • hooks/useIsRestoringConnection.ts — now derives restoring state from AppKit's useAppKitState (loading/initialized) instead of the deleted custom lifecycle store.

App-side wiring

  • Updaters.tsx — imports WidgetStandaloneModeUpdater from @cowprotocol/wallet and passes standaloneMode from useInjectedWidgetParams().
  • Trade form validation — new TradeFormValidation.RestoringWallet state. When there's no account because the wallet is still restoring, the button now shows "Restoring wallet" instead of "Connect wallet" (threaded through useTradeFormValidationContext → validateTradeForm → tradeButtonsMap).
  • vite.config.mts — adds an esbuild plugin (cow-reown-strip-sourcemap) that strips sourceMappingURL pragmas from @reown files to stop devtools source-map 404s.
  • WalletProvider now imports reownAppKit from wagmi/config (the old reown/init is gone).

In short: a refactor that trades a fragile custom wallet-lifecycle layer for Reown AppKit's native management, while adding a "Restoring wallet" UX state and a dev-tooling source-map fix.

To Test

CoW Swap

  1. Connect to WalletConnect
  2. Reload the page
  • it should restore the wallet connection after reloading
  1. Connect a wallet
  2. Disconnect it
  3. Reload the page
  • the app should stay disconnected from the wallet
  1. Try switching networks in UI
  2. Try switching networks in connected wallet

Safe

  1. Run https://github.com/safe-global/safe-wallet-monorepo/tree/dev/apps/web locally
  2. Add baseUrl: 'http://localhost:3000' in SwapWidget of the app
  3. Mock getIsSafeAppIframe to always return true in CoW Swap
  4. Open Safe locally
  • it should not crash
  1. Open /swap
  • The wallet should be connected. There should not be an option to disconnect it
  1. Try to swap
  • Order signing request should come to the Safe

Widget standalone

  1. Open widget and switch to standalone mode
  • It should not be connected to any wallet
  1. Connect any wallet
  2. Disconnect it
  3. Try another wallet

Widget dappMode

  1. Open widget, the configurator should not be connected to any wallet
  • the widget also is not connected, there is no option to connect wallet in the widget iframe
  1. Connect widget configurator to a wallet
  • the widget should become connected to widget wallet (the wallet name should be displayed in the account modal)
  1. Try to make an order
  • the signing request should come to the connected wallet

Summary by CodeRabbit

  • New Features

    • Added "Restoring wallet" status display during wallet connection recovery.
  • Improvements

    • Enhanced wallet connector management in standalone widget mode.
    • Improved Safe App iframe wallet detection and connection handling.
    • Better wallet disconnection behavior across different application contexts.
  • Chores

    • Added AppKit controllers dependency for enhanced wallet management.
    • Refactored wallet configuration and initialization logic.

@shoom3301 shoom3301 self-assigned this Jun 10, 2026
@shoom3301 shoom3301 added the RELEASE Included in the release that is being closed label Jun 10, 2026
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 15, 2026 4:52pm
explorer-dev Ready Ready Preview Jun 15, 2026 4:52pm
storybook Ready Ready Preview Jun 15, 2026 4:52pm
swap-dev Ready Ready Preview Jun 15, 2026 4:52pm
widget-configurator Ready Ready Preview Jun 15, 2026 4:52pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 15, 2026 4:52pm
sdk-tools Ignored Ignored Preview Jun 15, 2026 4:52pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

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: a63b60ca-f14a-4378-b104-a44b7b3aa15c

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
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat(wallet): use reown for wallets management' accurately summarizes the main objective: refactoring wallet management to use Reown AppKit instead of custom lifecycle code.
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 is comprehensive and well-structured. It includes a clear summary, detailed explanations of what was deleted and added, key implementation details with numbered importance markers, and extensive testing instructions organized by scenario (CoW Swap, Safe, Widget standalone, Widget dappMode).

✏️ 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 fix/wallets

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: d46928b
Status: ✅  Deploy successful!
Preview URL: https://f126af42.explorer-dev-dxz.pages.dev
Branch Preview URL: https://fix-wallets.explorer-dev-dxz.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: d46928b
Status: ✅  Deploy successful!
Preview URL: https://4928a1a8.swap-dev-5u6.pages.dev
Branch Preview URL: https://fix-wallets.swap-dev-5u6.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/wallet/src/index.ts`:
- Around line 43-44: The file exports './api/state' twice in
libs/wallet/src/index.ts; remove the duplicate export line so there is only a
single "export * from './api/state'" entry to avoid redundant re-exports and
potential linter warnings.

In `@libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx`:
- Line 84: Fix the typo in the inline comment inside
WidgetStandaloneMode.updater (file: WidgetStandaloneMode.updater.tsx) by
changing "I dapp mode never connect to widget connector" to "In dapp mode, never
connect to widget connector" (or similar grammatically correct phrasing); update
the comment text where it appears in the WidgetStandaloneMode updater block to
include the leading "n" and add a comma after "mode" for clarity.
- Around line 51-58: The effect that runs on isDappMode currently calls
reownAppKit.disconnect and connectWalletById(COW_WIDGET_CONNECTOR_ID) every time
isDappMode flips to true; add a persistent ref guard (e.g.,
dappConnectAttemptedRef) checked inside the useEffect so the connectWalletById
call only runs the first time isDappMode becomes true, set the ref to true when
initiating the connection, and do not reset it when isDappMode goes false so
repeated toggles won’t trigger additional calls; update the useEffect
surrounding isDappMode, reownAppKit.disconnect, and connectWalletById to use
this ref guard.
- Around line 82-85: The comment above the WidgetStandaloneMode updater
contradicts the component docs and logic: update the comment to state that in
standalone mode the widget connector must never be used (i.e., we disallow
connecting to the widget connector), matching the main component documentation
and the implementation in the WidgetStandaloneMode updater.

In `@libs/wallet/src/wagmi/config.ts`:
- Around line 113-114: The call to connectWalletById(SAFE_CONNECTOR_ID) inside
the getIsSafeAppIframe() branch drops its returned Promise and can cause
unhandled rejections; update the code in the module that calls connectWalletById
to explicitly handle the Promise (either await it inside an async init function
or append .catch(...)) and surface/log the error instead of letting it bubble as
an unhandled rejection so failures during Safe iframe boot are handled
gracefully. Ensure you reference the existing getIsSafeAppIframe(),
connectWalletById(), and SAFE_CONNECTOR_ID symbols when adding the try/catch or
.catch handler.
- Line 57: Keep the widget connector included via connectors: getConnectors()
(the standalone hiding is already handled in WidgetStandaloneMode.updater.tsx by
filtering COW_WIDGET_CONNECTOR_ID on ConnectorController and calling
wagmiAdapter.syncConnections()), and fix the unhandled promise from
connectWalletById(SAFE_CONNECTOR_ID) by moving the auto-connect into an async
init handler (or an IIFE) and await it with try/catch (or attach .catch) to
log/handle errors instead of letting the promise reject unhandled; reference
getConnectors(), connectWalletById(SAFE_CONNECTOR_ID),
WidgetStandaloneMode.updater.tsx, ConnectorController, COW_WIDGET_CONNECTOR_ID,
and wagmiAdapter.syncConnections() when locating the related code.
🪄 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: 2a211bf3-822d-4074-bdce-1372cffe30b5

📥 Commits

Reviewing files that changed from the base of the PR and between 7b7d528 and 15d746f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • apps/cowswap-frontend/package.json
  • apps/cowswap-frontend/src/locales/en-US.po
  • apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx
  • apps/cowswap-frontend/src/modules/injectedWidget/index.ts
  • apps/cowswap-frontend/src/modules/injectedWidget/updaters/WidgetStandaloneMode.updater.tsx
  • apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx
  • apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts
  • apps/cowswap-frontend/vite.config.mts
  • libs/wallet/package.json
  • libs/wallet/src/api/container/WalletProvider/index.tsx
  • libs/wallet/src/constants.ts
  • libs/wallet/src/index.ts
  • libs/wallet/src/reown/consts.ts
  • libs/wallet/src/reown/init.ts
  • libs/wallet/src/updaters/WidgetStandaloneMode.updater.test.tsx
  • libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx
  • libs/wallet/src/utils/connectWalletById.ts
  • libs/wallet/src/utils/getIsSafeAppIframe.ts
  • libs/wallet/src/wagmi/SafeConnectionHandler.tsx
  • libs/wallet/src/wagmi/Web3Provider.tsx
  • libs/wallet/src/wagmi/config.ts
  • libs/wallet/src/wagmi/getConnectors.ts
  • libs/wallet/src/wagmi/hooks/useDisconnectWallet.ts
  • libs/wallet/src/wagmi/hooks/useIsRestoringConnection.ts
  • libs/wallet/src/wagmi/initialReconnectLifecycle.ts
  • libs/wallet/src/wagmi/providerIsolation.test.ts
  • libs/wallet/src/wagmi/providerIsolation.ts
  • libs/wallet/src/wagmiStorage.ts
💤 Files with no reviewable changes (8)
  • libs/wallet/src/constants.ts
  • apps/cowswap-frontend/src/modules/injectedWidget/index.ts
  • libs/wallet/src/wagmi/providerIsolation.ts
  • libs/wallet/src/reown/init.ts
  • libs/wallet/src/wagmi/providerIsolation.test.ts
  • libs/wallet/src/wagmi/initialReconnectLifecycle.ts
  • libs/wallet/src/wagmi/SafeConnectionHandler.tsx
  • apps/cowswap-frontend/src/modules/injectedWidget/updaters/WidgetStandaloneMode.updater.tsx

Comment thread libs/wallet/src/index.ts
Comment thread libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx Outdated
Comment thread libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx
Comment thread libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx Outdated
Comment thread libs/wallet/src/wagmi/config.ts
Comment thread libs/wallet/src/wagmi/config.ts Outdated
})
},
},
{

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixes @reown/appkit source maps, now you can debug it locally

@alfetopito

Copy link
Copy Markdown
Collaborator

Tests passed on both Wallet Connect and EOA.

1 Failed when disconnected on the following steps, which differ from prod:

  1. Using the sell token dropdown selector, pick a chain other than the current one
  2. Pick a token other than the default one one (wrapped native)
    ER: The network is changed and the picked token is selected as sell
    AR: The network is changed but the token selection is reset to the default

In the widget however it works as expected

2 Widget starts with restoring wallet

image
  1. Load widget preview
    ER: connect wallet button is shown
    AR: restoring wallet is shown instead

It only goes away after switching to dapp mode and connecting the wallet outside of the context of the app

3 Standalone wallet connection is cleared after a refresh (same as widget.cow.fi)

This is not a new behaviour, but mentioning just in case

  1. Open widget preview
  2. Switch to standalone mode
  3. Connect wallet inside the app
  4. Refresh the page
    ER: Connection is restored when switching over to standalone mode
    AR: Connection is not restored

4 Widget dapp mode connection is remembered

Not a new behaviour, same as widget.cow.fi. Mentioning as it was one of the test steps

  1. Open widget preview
  2. Connect wallet in dappmode
  3. Refresh page
    ER: Wallet is not connected
    AR: Wallet is connected

Tested everything but the Safe widget so far.

Comment thread libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx
Comment thread libs/wallet/src/utils/connectWalletById.ts Outdated

export const wagmiStorage = createStorage({
key: WAGMI_STORAGE_KEY,
storage: localStorage,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have a Claude comment regarding this change:

sessionStorage → localStorage: tab isolation is silently removed

wagmiStorage.ts now uses localStorage where the old code explicitly used sessionStorage. The old code had extensive comments explaining why sessionStorage was chosen: "Tab A stays on Rabby even if Tab B switches to MetaMask". Connecting in one tab now affects all tabs. If this is intentional (relying on AppKit's own isolation), it should be explicitly documented; if not, it's a regression.

I recall this being one issue the Bleu team spent a while fixing raised by Elena, as this was a regression given the current prod behaviour.

Is this change intentional?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it was intentional.
wagmiStorage should have storage: localStorage to make the reconnect correct. Before, it was sessionStorage for multi-tab wallet isolation, but it didn't really work.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok. If this behaviour cannot be restored, we better make @elena-zh aware as this will affect her tests.

@elena-zh elena-zh left a comment

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.

Hey @shoom3301 , thank you!

Some issues:

  1. Safe swap feature: it shows 'connect wallet' button if I setup everything based on the instructions
image
  1. Widget dapp mode: it shows 'restoring wallet ' state when I'm disconnected:
  • open the dapp mode
  • connect to a wallet
  • disconnect a wallet -->
image

Expected: show grayed out 'connect wallet'button

  1. same issue with the standalone mode :( .
    To reproduce:
  • make sure that the preview link for the CoW Swap app is not open!
  • open widget configurator in the standalone mode
    Expected result here: show an active 'connect wallet' button
image
  1. standalone mode: connection is not separated with the CoW Swap app (this issue was addressed in #7441)
  • open the widget standalone mode
  • open the cow swap app
  • connect to wallet in the widget --> the app gets automatically connected
  • disconnect a wallet in CoW Swap --> the app gets disconnected in the widget

Expected: connections should not interfere

5 CoW Swap app: connect wallet button may call this modal
image

Possible steps to reproduce (not always reproducible):
1 . connect to a wallet in the app
2. close the browser tab
3. reopen the browser tab
4. press on the 'connect wallet button after 'restoring wallet' message

@shoom3301

Copy link
Copy Markdown
Collaborator Author

@shoom3301

Copy link
Copy Markdown
Collaborator Author

@elena-zh 2nd is not reproducible for me. Do you test it with {"baseUrl": "https://93134493.swap-dev-5u6.pages.dev"}?

@shoom3301

shoom3301 commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

@elena-zh nevermind, I've hardcoded {"baseUrl": "https://93134493.swap-dev-5u6.pages.dev"} in this PR, so 2 and 3 should be fixed.

@alfetopito

Copy link
Copy Markdown
Collaborator

@elena-zh nevermind, I've hardcoded {"baseUrl": "https://93134493.swap-dev-5u6.pages.dev"} in this PR, so 2 and 3 should be fixed.

That hardcodes to a given build, not to the PR url. You should use https://fix-wallets.swap-dev-5u6.pages.dev/ instead.

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

Labels

RELEASE Included in the release that is being closed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants