Skip to content

feat(wallet): replace metamaskSDK with @metamask/connect-evm + mobile enablement#7236

Closed
adaOctopus wants to merge 6 commits intocowprotocol:developfrom
adaOctopus:metamask-connect
Closed

feat(wallet): replace metamaskSDK with @metamask/connect-evm + mobile enablement#7236
adaOctopus wants to merge 6 commits intocowprotocol:developfrom
adaOctopus:metamask-connect

Conversation

@adaOctopus
Copy link
Copy Markdown

@adaOctopus adaOctopus commented Apr 1, 2026

@metamask/connect-evm integration (replace metamaskSdk)

This PR replaces @metamask/sdk with @metamask/connect-evm for the web3-react MetaMask path. The app still uses ConnectionType.METAMASK and the same activation flow; only the underlying wallet client changes.

Implementation

  1. New MetaMaskConnect connector: lazy-loads @metamask/connect-evm, calls createEVMClient with dapp metadata, api.supportedNetworks (from existing RPC_URLS), and mobile: { useDeeplink: true } for mobile app opens.
  2. EIP-1193 provider from client.getProvider() is wired into web3-react with the usual event handlers (chainChanged, accountsChanged, etc.).
  3. getWeb3ReactConnection still maps ConnectionType.METAMASK to metaMaskConnectConnection.
  4. Mobile: the MetaMask Connect row is shown whenever there is no EIP-6963 injected MetaMask (io.metamask), so mobile browsers can use MetaMask Connect instead of being hidden behind an isMobile gate.
  5. Wagmi config continues to use metaMask() from @wagmi/connectors for the viem migration path (separate from this connector).

Docs

Quick overview of changes: libs/wallet/src/web3-react/connectors/metaMaskConnect/README.md.

Testing

  • Desktop: connect / switch network / sign / disconnect with MetaMask Connect (no extension) and with extension (no duplicate MetaMask row).
  • Mobile: Safari/Chrome without injected MetaMask; MetaMask in-app browser (injected only).

Summary by CodeRabbit

  • New Features

    • MetaMask wallet tile now shows on mobile when no injected MetaMask is present.
    • Added a new MetaMask Connect flow for more reliable connection and activation behavior.
  • Chores

    • Switched MetaMask integration to the newer Connect EVM package for improved compatibility and mobile deeplinking.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 1, 2026

@adaOctopus is attempting to deploy a commit to the cow-dev Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

Walkthrough

Replaces the MetaMask SDK integration with a MetaMask Connect (connect-evm) implementation across connector, UI, and exports; adds a new MetaMaskConnect connector and option; removes the legacy MetaMask SDK connector and option; updates detection logic, wagmi config, and package dependency.

Changes

Cohort / File(s) Summary
Frontend UI Updates
apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx
Replaced MetaMaskSdkOption with MetaMaskConnectOption; changed visibility gating from !hasInjectedMetaMask && !isMobile to !hasInjectedMetaMask.
Dependency Management
libs/wallet/package.json
Replaced @metamask/sdk with @metamask/connect-evm in dependencies.
Hook and Detection Logic
libs/wallet/src/api/hooks.ts
Extracted MetaMask detection into getMetaMaskConnectionInfo(...), adjusted logic to combine connector type and normalized name checks for MetaMask/injected identification.
Module Exports
libs/wallet/src/index.ts
Now re-exports MetaMaskConnectOption instead of the removed MetaMaskSdkOption.
Wagmi Configuration
libs/wallet/src/wagmi/config.ts
Added metaMask() connector to wagmi connectors list with dapp metadata and SSR-safe origin fallback.
New MetaMask Connect Integration
libs/wallet/src/web3-react/connection/metaMaskConnect.tsx, libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts
Added metaMaskConnectConnection and MetaMaskConnect connector: lazy-loads @metamask/connect-evm, creates EVM client, provides eager connect/activate/deactivate/watchAsset, and binds provider events; added related error/constructor types.
New MetaMask Connect UI & Docs
libs/wallet/src/web3-react/connection/metaMaskConnectOption.tsx, libs/wallet/src/web3-react/connectors/metaMaskConnect/README.md
Added MetaMaskConnectOption wallet tile component and README documenting migration, runtime wiring, and mobile UX rules.
Removed Legacy MetaMask SDK
libs/wallet/src/web3-react/connection/metaMaskSdk.tsx, libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts
Removed SDK-based connector and UI (metaMaskSdkConnection, MetaMaskSdkOption) and SDK connector implementation.
Connection Registry Update
libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts
Mapped ConnectionType.METAMASK to metaMaskConnectConnection (replaced metaMaskSdkConnection).
Types Update
libs/wallet/src/api/types.ts
Added METAMASK: metaMask.type to exported ConnectorType mapping.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as UI (MetaMaskConnectOption)
    participant Conn as Connection (metaMaskConnectConnection)
    participant Connector as MetaMaskConnect
    participant EVM as `@metamask/connect-evm` (EVM Client)
    participant MM as MetaMask Wallet

    User->>UI: click MetaMask tile
    UI->>Conn: tryActivation(connector)
    Conn->>Connector: activate()
    Connector->>EVM: lazy-load & createEVMClient(dappMetadata, readonlyRPCMap)
    EVM->>MM: init/connect (deeplink or popup)
    MM-->>EVM: provider / user approves
    EVM-->>Connector: { accounts, chainId }
    Connector->>Conn: update accounts/chainId (activation success)
    Connector->>Connector: bind provider events (accountsChanged, chainChanged, disconnect)
    Conn-->>UI: mark active
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐰 I hopped and swapped the old SDK away,

A sleeker connect now lights the way.
Tiles bloom on phones where none would show,
New client in, old bindings go.
Tiny hop, big tasty deploy—🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: replacing @metamask/sdk with @metamask/connect-evm and enabling mobile support, which aligns with the extensive refactoring across the codebase.
Description check ✅ Passed The PR description is comprehensive with implementation details, docs reference, and testing instructions, though it deviates from the template format by omitting sections like 'Summary', 'To Test' checkboxes, and 'Background'.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (2)
libs/wallet/src/web3-react/connection/metaMaskConnect.tsx (1)

19-22: Avoid hardcoding the dapp URL in connector metadata.

Using a fixed origin here can desync metadata in non-production hosts. Prefer the same runtime-origin fallback pattern used in the wagmi config.

Suggested patch
         dappMetadata: {
           name: 'CoW Swap',
-          url: 'https://swap.cow.fi',
+          url: typeof window === 'undefined' ? 'https://swap.cow.fi' : window.location.origin,
         },
As per coding guidelines: "Use shared config/enums instead of hardcoding environment-specific lists/toggles".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/wallet/src/web3-react/connection/metaMaskConnect.tsx` around lines 19 -
22, The dappMetadata.url is hardcoded ("https://swap.cow.fi"); update the
connector setup (where dappMetadata is defined in metaMaskConnect) to use the
runtime-origin fallback pattern instead of a fixed URL — e.g., derive the URL
from window.location.origin (or the same helper/constant used in your wagmi
config) so non-production hosts inherit their actual origin; replace the
hardcoded string in dappMetadata with that runtime-derived value.
libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts (1)

261-263: Remove unnecessary catch-rethrow block.

The catch (error) { throw error } pattern adds no value—it catches and immediately rethrows without any error transformation or logging. Remove the try-catch wrapper or add meaningful error handling.

Proposed fix
-    try {
-      await this.client.switchChain({
-        chainId: chainIdHex,
-        chainConfiguration:
-          desiredChain !== undefined
-            ? {
-                chainName: desiredChain.chainName,
-                nativeCurrency: desiredChain.nativeCurrency,
-                rpcUrls: desiredChain.rpcUrls,
-                blockExplorerUrls: desiredChain.blockExplorerUrls,
-              }
-            : undefined,
-      })
-    } catch (error) {
-      throw error
-    }
+    await this.client.switchChain({
+      chainId: chainIdHex,
+      chainConfiguration:
+        desiredChain !== undefined
+          ? {
+              chainName: desiredChain.chainName,
+              nativeCurrency: desiredChain.nativeCurrency,
+              rpcUrls: desiredChain.rpcUrls,
+              blockExplorerUrls: desiredChain.blockExplorerUrls,
+            }
+          : undefined,
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts` around lines
261 - 263, There is an unnecessary catch-and-rethrow block that wraps an async
operation (the pattern `catch (error) { throw error }`); remove the try/catch
wrapper around that operation (or replace it with meaningful handling such as
logging via processLogger or wrapping with a custom Error) so the function (in
the metaMaskConnect connector where the `catch (error) { throw error }` appears)
relies on natural propagation or emits helpful context; update any tests/callers
if they relied on the previous try/catch behavior.
🤖 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/api/hooks.ts`:
- Around line 173-177: Replace the hardcoded 'metaMaskSDK' string by relying on
the upstream connector symbol: import the MetaMask connector (e.g.,
MetaMaskConnector) or use the connector's reported name instead of a literal;
then compute isMetaMaskConnectorId by checking the connector instance/type
(e.g., connector instanceof MetaMaskConnector or connector.id ===
MetaMaskConnector.id or connector.name === 'MetaMask') and keep
isMetamaskConnection and isInjectedConnection using that variable and
ConnectorType.INJECTED; update the import and the isMetaMaskConnectorId
assignment so we no longer depend on the 'metaMaskSDK' literal.

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts`:
- Around line 88-91: The dappMetadata fallback sets the name to 'wagmi'
incorrectly; update the default dapp name in the dappMetadata object (the code
that computes options?.dappMetadata ?? { url: defaultUrl, name: defaultUrl !==
'' ? undefined : 'wagmi' }) to use 'CoW Swap' instead of 'wagmi' so the
connector's dapp name falls back to "CoW Swap" when defaultUrl is empty.
- Around line 166-180: The connectEagerly method starts an activation with const
cancelActivation = this.actions.startActivation() but the catch block only calls
this.actions.resetState(), leaving the activation pending; update the catch (or
use a finally) to call cancelActivation() before resetting state (i.e., invoke
cancelActivation() in the catch/finally of connectEagerly so the activation
started by this.actions.startActivation() is always cancelled, then call
this.actions.resetState()).
- Around line 115-128: The call to createEVMClient includes an unsupported
mobile.useDeeplink option that is misleading; remove the entire mobile block
(the object setting useDeeplink) from the createEVMClient invocation in index.ts
so only the documented keys (dapp, api, eventHandlers) are passed, keeping the
dapp metadata assembly (this.options.dappMetadata.name/url/iconUrl) and the api
supportedNetworks code unchanged.

---

Nitpick comments:
In `@libs/wallet/src/web3-react/connection/metaMaskConnect.tsx`:
- Around line 19-22: The dappMetadata.url is hardcoded ("https://swap.cow.fi");
update the connector setup (where dappMetadata is defined in metaMaskConnect) to
use the runtime-origin fallback pattern instead of a fixed URL — e.g., derive
the URL from window.location.origin (or the same helper/constant used in your
wagmi config) so non-production hosts inherit their actual origin; replace the
hardcoded string in dappMetadata with that runtime-derived value.

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts`:
- Around line 261-263: There is an unnecessary catch-and-rethrow block that
wraps an async operation (the pattern `catch (error) { throw error }`); remove
the try/catch wrapper around that operation (or replace it with meaningful
handling such as logging via processLogger or wrapping with a custom Error) so
the function (in the metaMaskConnect connector where the `catch (error) { throw
error }` appears) relies on natural propagation or emits helpful context; update
any tests/callers if they relied on the previous try/catch behavior.
🪄 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: d7093e6e-dfd9-4e17-a41d-c2a269b94f01

📥 Commits

Reviewing files that changed from the base of the PR and between a8da98b and 3f3683e.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx
  • libs/wallet/package.json
  • libs/wallet/src/api/hooks.ts
  • libs/wallet/src/index.ts
  • libs/wallet/src/wagmi/config.ts
  • libs/wallet/src/web3-react/connection/metaMaskConnect.tsx
  • libs/wallet/src/web3-react/connection/metaMaskConnectOption.tsx
  • libs/wallet/src/web3-react/connection/metaMaskSdk.tsx
  • libs/wallet/src/web3-react/connectors/metaMaskConnect/README.md
  • libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts
  • libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts
  • libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts
💤 Files with no reviewable changes (2)
  • libs/wallet/src/web3-react/connection/metaMaskSdk.tsx
  • libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts

Comment thread libs/wallet/src/api/hooks.ts Outdated
Comment thread libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts
Comment thread libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts
Comment thread libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts
@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 1, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedhusky@​9.1.71001006280100
Addedbuffer@​6.0.39910010075100
Added@​types/​ms@​2.1.01001009480100
Addedtiny-invariant@​1.3.310010010080100
Addedjsbi@​3.2.51001009983100
Addedfraction.js@​4.3.710010010083100
Addedcsstype@​3.1.310010010084100
Addedeventemitter3@​4.0.71001009985100
Addedexponential-backoff@​3.1.31001009885100
Addedajv@​6.12.6999910086100

View full report

@adaOctopus
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Apr 1, 2026
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: 3

🤖 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/web3-react/connectors/metaMaskConnect/index.ts`:
- Around line 137-148: The disconnect listener attached in
provider.on('disconnect') can reject because the fallback probe
(this.provider.request({ method: 'eth_accounts' })) may fail; modify the async
listener (the anonymous function passed to provider.on) to wrap the eth_accounts
probe in a try/catch so probe failures are swallowed and execution falls through
to the teardown logic: call getOriginalRpcError(error) as before, attempt the
accounts probe inside try { const accounts = await this.provider.request(...) }
catch { /* treat as no accounts; continue */ }, keep the existing short-circuit
return when accounts.length > 0, and ensure this.actions.resetState() and
this.onError?.(error) always run on real disconnects.
- Around line 109-163: The isomorphicInitialize() path currently does "if
(this.eagerConnection) return" which returns undefined instead of the shared
promise and never clears the memo on errors; change that to return
this.eagerConnection so callers await the in-flight init, and wrap the
import(...).then(...) chain with a .catch(err => { this.eagerConnection =
undefined; throw err }) so a transient failure clears this.eagerConnection;
update the block that assigns this.eagerConnection (the Promise that imports
'@metamask/connect-evm' and calls createEVMClient/getProvider and provider.on
handlers) to return the Promise and ensure rejections reset
this.eagerConnection.
- Around line 33-40: Update the MetaMaskConnectOptions.readonlyRPCMap type to
require hex-prefixed chain IDs (use Record<`0x${string}`, string>) and remove
any misleading runtime casts; then add/ensure validation at the call site that
builds readonlyRPCMap (the place currently casting keys to hex) so only
hex-prefixed keys are passed into the connector. Refer to the
MetaMaskConnectOptions type and the readonlyRPCMap usage/cast in the connector
to locate the changes and ensure the type and runtime validation are consistent.
🪄 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: 92b213a1-b99c-4f57-aebe-2bb97046546c

📥 Commits

Reviewing files that changed from the base of the PR and between d21104f and 5ed0c72.

📒 Files selected for processing (3)
  • libs/wallet/src/api/hooks.ts
  • libs/wallet/src/api/types.ts
  • libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts

Comment on lines +33 to +40
type MetaMaskConnectOptions = {
dappMetadata: {
name?: string
url?: string
iconUrl?: string
}
readonlyRPCMap?: Record<string, string>
}
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -type f -name "*.ts" -path "*metaMaskConnect*"

Repository: cowprotocol/cowswap

Length of output: 233


🏁 Script executed:

fd -type f "metaMaskConnect" -o -path "*web3-react*" | grep -i metamask

Repository: cowprotocol/cowswap

Length of output: 233


🏁 Script executed:

ls -la libs/wallet/src/web3-react/connectors/metaMaskConnect/

Repository: cowprotocol/cowswap

Length of output: 291


🏁 Script executed:

rg "metaMaskConnect" --type ts --files

Repository: cowprotocol/cowswap

Length of output: 118


🏁 Script executed:

git ls-files | grep -i metamask

Repository: cowprotocol/cowswap

Length of output: 451


🏁 Script executed:

git ls-files "libs/wallet/*" | head -30

Repository: cowprotocol/cowswap

Length of output: 1236


🏁 Script executed:

find . -name "index.ts" -type f | xargs grep -l "MetaMaskConnectOptions" 2>/dev/null

Repository: cowprotocol/cowswap

Length of output: 127


🏁 Script executed:

rg "MetaMaskConnectOptions" --type ts -l

Repository: cowprotocol/cowswap

Length of output: 125


🏁 Script executed:

rg "readonlyRPCMap" --type ts -l

Repository: cowprotocol/cowswap

Length of output: 183


🏁 Script executed:

rg "supportedNetworks" --type ts -B 2 -A 2

Repository: cowprotocol/cowswap

Length of output: 15274


🏁 Script executed:

find . -type f -name "*.ts" | xargs grep -l "supportedNetworks" 2>/dev/null | head -5

Repository: cowprotocol/cowswap

Length of output: 284


🏁 Script executed:

rg "0x.*string" --type ts | grep supportedNetworks | head -5

Repository: cowprotocol/cowswap

Length of output: 226


🏁 Script executed:

rg "supportedNetworks.*readonlyRPCMap" --type ts -A 5 -B 5

Repository: cowprotocol/cowswap

Length of output: 1316


🏁 Script executed:

rg "RPC_URLS" --type ts -l

Repository: cowprotocol/cowswap

Length of output: 599


🏁 Script executed:

git ls-files | grep -E "\.ts$" | head -20

Repository: cowprotocol/cowswap

Length of output: 793


🏁 Script executed:

git log --oneline -1 -- "libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts"

Repository: cowprotocol/cowswap

Length of output: 166


🏁 Script executed:

cat libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts 2>/dev/null | head -150 || echo "File not found; listing repo structure:"

Repository: cowprotocol/cowswap

Length of output: 5339


🏁 Script executed:

find . -maxdepth 3 -type d -name wallet 2>/dev/null

Repository: cowprotocol/cowswap

Length of output: 76


🏁 Script executed:

find . -type d -name connectors 2>/dev/null | head -5

Repository: cowprotocol/cowswap

Length of output: 102


🏁 Script executed:

find . -type f -name "index.ts" 2>/dev/null | wc -l

Repository: cowprotocol/cowswap

Length of output: 66


🏁 Script executed:

rg "readonlyRPCMap|MetaMaskConnectOptions" --type ts --max-count 20

Repository: cowprotocol/cowswap

Length of output: 740


🏁 Script executed:

pwd

Repository: cowprotocol/cowswap

Length of output: 81


🏁 Script executed:

rg "chainId.*toString.*16|0x.*Number.*toString" --type ts | head -10

Repository: cowprotocol/cowswap

Length of output: 999


🏁 Script executed:

rg "SupportedChainId" --type ts | grep Record | head -10

Repository: cowprotocol/cowswap

Length of output: 1574


🏁 Script executed:

rg "getAddressKey|chainToHex|toHex" --type ts -l | head -10

Repository: cowprotocol/cowswap

Length of output: 599


🏁 Script executed:

rg "supportedNetworks" --type ts -B 5 -A 2 | head -50

Repository: cowprotocol/cowswap

Length of output: 3993


🏁 Script executed:

rg "0x[0-9a-f]+.*string" --type ts | head -10

Repository: cowprotocol/cowswap

Length of output: 378


🏁 Script executed:

rg "as Record.*0x" --type ts

Repository: cowprotocol/cowswap

Length of output: 226


🏁 Script executed:

git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -i metamask

Repository: cowprotocol/cowswap

Length of output: 45


🏁 Script executed:

git log --oneline -5 2>/dev/null || echo "Not a git repo or git unavailable"

Repository: cowprotocol/cowswap

Length of output: 166


🏁 Script executed:

cat -n libs/wallet/src/web3-react/connection/metaMaskConnect.tsx | head -50

Repository: cowprotocol/cowswap

Length of output: 1437


🏁 Script executed:

rg "RPC_URLS" --type ts -l | head -10

Repository: cowprotocol/cowswap

Length of output: 565


🏁 Script executed:

rg "export.*RPC_URLS" --type ts -B 2 -A 5

Repository: cowprotocol/cowswap

Length of output: 696


🏁 Script executed:

rg "RPC_URLS\[" --type ts -B 1 -A 1

Repository: cowprotocol/cowswap

Length of output: 1986


🏁 Script executed:

rg "RPC_URLS.*=" --type ts | head -5

Repository: cowprotocol/cowswap

Length of output: 353


🏁 Script executed:

rg "RPC_URLS" --type ts | grep "Record\|Map\|{" | head -10

Repository: cowprotocol/cowswap

Length of output: 1172


🏁 Script executed:

cat libs/wallet/src/web3-react/connection/metaMaskConnect.tsx

Repository: cowprotocol/cowswap

Length of output: 1192


🏁 Script executed:

rg "const metaMaskConnectInitializedAtom|export.*metaMaskConnect" --type ts -A 10 libs/wallet/src/web3-react/connection/

Repository: cowprotocol/cowswap

Length of output: 506


🏁 Script executed:

grep -n "readonlyRPCMap" libs/wallet/src/web3-react/connection/metaMaskConnect.tsx

Repository: cowprotocol/cowswap

Length of output: 109


🏁 Script executed:

grep -n "Object.fromEntries" libs/wallet/src/web3-react/connection/metaMaskConnect.tsx -A 3 -B 3

Repository: cowprotocol/cowswap

Length of output: 328


🏁 Script executed:

sed -n '1,100p' libs/wallet/src/web3-react/connection/metaMaskConnect.tsx | cat -n

Repository: cowprotocol/cowswap

Length of output: 1437


🏁 Script executed:

head -100 libs/wallet/src/web3-react/connection/metaMaskConnect.tsx

Repository: cowprotocol/cowswap

Length of output: 1192


🏁 Script executed:

rg "fromEntries.*chainId.*0x" libs/wallet/src/web3-react/ -B 3 -A 3

Repository: cowprotocol/cowswap

Length of output: 45


🏁 Script executed:

rg "0x.*Number.*toString.*16" --type ts -B 5 -A 2 | grep -A 7 "readonlyRPCMap\|metaMaskConnect"

Repository: cowprotocol/cowswap

Length of output: 795


🏁 Script executed:

grep -A 5 -B 5 "readonlyRPCMap:" libs/wallet/src/web3-react/connection/metaMaskConnect.tsx

Repository: cowprotocol/cowswap

Length of output: 371


🏁 Script executed:

rg "MetaMaskConnectOptions" --type ts -B 2 -A 2

Repository: cowprotocol/cowswap

Length of output: 1516


Strengthen the type definition to document the hex-format requirement.

While the call site correctly transforms RPC_URLS keys to hex format before passing readonlyRPCMap, the type definition Record<string, string> doesn't enforce this contract. The cast at line 121 is a no-op at runtime and misleads developers into thinking the transformation happens in the connector rather than at the call site. If another caller is added without the transformation, the connector would silently receive malformed chain IDs.

Either update the type to Record<0x${string}, string> with matching validation at the call site, or document the hex-format requirement explicitly in the type definition's JSDoc comment.

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

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts` around lines
33 - 40, Update the MetaMaskConnectOptions.readonlyRPCMap type to require
hex-prefixed chain IDs (use Record<`0x${string}`, string>) and remove any
misleading runtime casts; then add/ensure validation at the call site that
builds readonlyRPCMap (the place currently casting keys to hex) so only
hex-prefixed keys are passed into the connector. Refer to the
MetaMaskConnectOptions type and the readonlyRPCMap usage/cast in the connector
to locate the changes and ensure the type and runtime validation are consistent.

Comment on lines +109 to +163
if (this.eagerConnection) return

this.eagerConnection = import('@metamask/connect-evm').then(async (module) => {
if (!this.client) {
// Library entry point: builds the multichain client,
// wires mobile deeplinks when not shown in the extension.
this.client = await module.createEVMClient({
dapp: {
name: this.options.dappMetadata.name ?? 'CoW Swap',
url: this.options.dappMetadata.url ?? '',
...(this.options.dappMetadata.iconUrl !== undefined ? { iconUrl: this.options.dappMetadata.iconUrl } : {}),
},
api: {
supportedNetworks: (this.options.readonlyRPCMap ?? {}) as Record<`0x${string}`, string>,
},
// createEVMClient is typed to accept MultichainOptions.mobile (see @metamask/connect-evm connect.d.ts).
mobile: {
useDeeplink: true,
},
})
}

this.provider = this.client.getProvider() as unknown as MetaMaskProvider

this.provider.on('connect', (({ chainId }: ProviderConnectInfo): void => {
this.actions.update({ chainId: parseChainId(chainId) })
}) as Listener)

this.provider.on('disconnect', (async (error: ProviderRpcError): Promise<void> => {
const originalError = getOriginalRpcError(error)

// MetaMask may emit transient disconnects; keep session if accounts still available.
if (error && originalError.code === 1013 && this.provider) {
const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[]
if (accounts.length > 0) return
}

this.actions.resetState()
this.onError?.(error)
}) as Listener)

this.provider.on('chainChanged', ((chainId: string): void => {
this.actions.update({ chainId: parseChainId(chainId) })
}) as Listener)

this.provider.on('accountsChanged', ((accounts: string[]): void => {
if (accounts.length === 0) {
this.actions.resetState()
} else {
this.actions.update({ accounts })
}
}) as Listener)
})

return this.eagerConnection
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

Return the shared init promise instead of early-resolving.

Line 109 currently exits isomorphicInitialize() without awaiting the in-flight initialization. Since connectEagerly() is started from libs/wallet/src/web3-react/Web3Provider/hooks/useEagerlyConnect.ts:24-38, a user-triggered activate() can overlap with it and observe this.provider as unset even though the SDK import/client setup is still running. The memoized promise is also never cleared after a rejection, so one transient init failure bricks the connector for the rest of the session.

Suggested fix
  private async isomorphicInitialize(): Promise<void> {
-    if (this.eagerConnection) return
+    if (this.eagerConnection) return this.eagerConnection

     this.eagerConnection = import('@metamask/connect-evm')
       .then(async (module) => {
         // existing initialization body
       })
+      .catch((error) => {
+        this.eagerConnection = undefined
+        throw error
+      })

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

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts` around lines
109 - 163, The isomorphicInitialize() path currently does "if
(this.eagerConnection) return" which returns undefined instead of the shared
promise and never clears the memo on errors; change that to return
this.eagerConnection so callers await the in-flight init, and wrap the
import(...).then(...) chain with a .catch(err => { this.eagerConnection =
undefined; throw err }) so a transient failure clears this.eagerConnection;
update the block that assigns this.eagerConnection (the Promise that imports
'@metamask/connect-evm' and calls createEVMClient/getProvider and provider.on
handlers) to return the Promise and ensure rejections reset
this.eagerConnection.

Comment on lines +137 to +148
this.provider.on('disconnect', (async (error: ProviderRpcError): Promise<void> => {
const originalError = getOriginalRpcError(error)

// MetaMask may emit transient disconnects; keep session if accounts still available.
if (error && originalError.code === 1013 && this.provider) {
const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[]
if (accounts.length > 0) return
}

this.actions.resetState()
this.onError?.(error)
}) as Listener)
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

Catch probe failures inside the disconnect handler.

On a real disconnect, the fallback eth_accounts request can fail too. When that happens, this async listener rejects before Lines 146-147 run, so wallet state is never reset on the disconnect path. Catch the probe locally and fall through to the normal teardown branch.

Suggested fix
         if (error && originalError.code === 1013 && this.provider) {
-          const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[]
-          if (accounts.length > 0) return
+          try {
+            const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[]
+            if (accounts.length > 0) return
+          } catch {
+            // fall through to reset state when the probe itself fails
+          }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/wallet/src/web3-react/connectors/metaMaskConnect/index.ts` around lines
137 - 148, The disconnect listener attached in provider.on('disconnect') can
reject because the fallback probe (this.provider.request({ method:
'eth_accounts' })) may fail; modify the async listener (the anonymous function
passed to provider.on) to wrap the eth_accounts probe in a try/catch so probe
failures are swallowed and execution falls through to the teardown logic: call
getOriginalRpcError(error) as before, attempt the accounts probe inside try {
const accounts = await this.provider.request(...) } catch { /* treat as no
accounts; continue */ }, keep the existing short-circuit return when
accounts.length > 0, and ensure this.actions.resetState() and
this.onError?.(error) always run on real disconnects.

@shoom3301
Copy link
Copy Markdown
Collaborator

Hi! Thanks a lot for your contribution!
We are close to integrate Reown, so this change will not be relevant, sorry.
#7061

@shoom3301 shoom3301 closed this Apr 6, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants