From e20047c2845a38b5ead38e05c238640de8ad0dd6 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 10:29:57 +0100 Subject: [PATCH 01/21] docs: plan connect evm integration --- .../specs/2026-05-28-connect-evm-design.md | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-28-connect-evm-design.md diff --git a/docs/superpowers/specs/2026-05-28-connect-evm-design.md b/docs/superpowers/specs/2026-05-28-connect-evm-design.md new file mode 100644 index 00000000..8c72221d --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-connect-evm-design.md @@ -0,0 +1,216 @@ +# MetaMask Connect EVM Test Dapp Design + +## Goal + +Add an optional MetaMask Connect EVM connection path to the test dapp without replacing or regressing the existing `window.ethereum`, EIP-6963, MetaMask SDK, or WalletConnect flows. + +The user-facing result is an additional `Connect EVM` button in the existing Connect Actions card. Clicking it connects through the published `@metamask/connect-evm` package and makes its EIP-1193 provider the active provider used by the rest of the page. + +## Non-Goals + +- Do not remove `@metamask/sdk` or change the behavior of the existing `SDK Connect` button. +- Do not replace WalletConnect/Web3Modal. +- Do not make `connect-evm` appear as an EIP-6963 provider unless the package itself provides that support. +- Do not convert the app to React, TypeScript, wagmi, viem, or a new app architecture. +- Do not require an Infura API key for local development. + +## Current State + +The dapp already centralizes most wallet interactions through `globalContext.provider`. That makes an additional provider source feasible, because existing transaction, signature, permissions, and network components call the active provider through the same EIP-1193 request surface. + +The current connection paths are: + +- `window.ethereum`, selected on startup when available. +- EIP-6963 provider discovery and manual selection. +- `@metamask/sdk` via `SDK Connect`. +- WalletConnect/Web3Modal via `Wallet Connect`. + +The main compatibility issues for `@metamask/connect-evm` are: + +- The app treats `globalContext.provider.isMetaMask` as the gate for installing listeners and enabling many controls. The `connect-evm` provider is EIP-1193 compatible but does not expose `isMetaMask`. +- Provider initialization calls `net_version`. `connect-evm` marks `net_version` unsupported, so initialization must derive the decimal network id from `eth_chainId` when `net_version` is unavailable. +- `connect-evm` needs an explicit `api.supportedNetworks` map of hex chain IDs to RPC URLs. + +## Approach + +Use an additive connection path: + +1. Add the published `@metamask/connect-evm` package. +2. Add a focused `src/connect-evm.js` module that owns `createEVMClient`, supported network configuration, connect, disconnect, and provider-detail creation. +3. Add a `Connect EVM` button to the existing Connect Actions card. +4. Wire the new button in `src/index.js` so a successful connection calls `setActiveProviderDetail()` with the `connect-evm` provider. +5. Relax provider capability checks from "is MetaMask installed" to "is this active provider usable by this dapp." +6. Keep legacy provider behavior unchanged where possible. + +## Supported Networks + +The first implementation supports a stable core set aligned with the dapp's existing sample-contract and network-switching coverage: + +- Ethereum Mainnet: `0x1` +- Sepolia: `0xaa36a7` +- OP Mainnet: `0xa` +- Polygon Mainnet: `0x89` +- Base Mainnet: `0x2105` +- Arbitrum One: `0xa4b1` +- Avalanche C-Chain: `0xa86a` +- Binance Smart Chain: `0x38` +- Localhost 8545: `0x539` +- Localhost 8546: `0x53a` + +Use public RPC URLs for public networks only where they are acceptable for a test dapp. Local chains use `http://127.0.0.1:8545` and `http://127.0.0.1:8546`. + +The network picker continues to display more chains than `connect-evm` supports. If a user selects an unsupported chain while `connect-evm` is active, the app shows the existing network error path instead of breaking the active provider state. + +## Components + +### `src/connect-evm.js` + +Responsibilities: + +- Import `createEVMClient` from `@metamask/connect-evm`. +- Keep a singleton client promise so repeated clicks do not create competing clients. +- Define `CONNECT_EVM_SUPPORTED_NETWORKS`. +- Define `CONNECT_EVM_CHAIN_IDS` from the supported network keys. +- Initialize the client with existing dapp metadata: + - `name: 'E2e Test Dapp'` + - `description: 'This is the E2e Test Dapp'` + - `url: 'https://metamask.github.io/test-dapp/'` +- Pass `api.supportedNetworks`. +- Expose `handleConnectEvm(name, button, isConnected)`. +- On connect: + - call `client.connect({ chainIds: CONNECT_EVM_CHAIN_IDS })` + - get `client.getProvider()` + - create a provider detail shaped like existing EIP-6963 details + - set it active + - update accounts and button state +- On disconnect: + - call `client.disconnect()` + - clear accounts and active UI state + - remove the provider detail + - restore button text and style + +### `src/components/connections/connections.js` + +Add a new button: + +- `id="connectEvm"` +- text: `Connect EVM` +- same existing button classes as `SDK Connect` and `Wallet Connect` + +Do not rearrange or rename existing controls. + +### `src/index.js` + +Responsibilities: + +- Import the `connect-evm` handler. +- Track `isConnectEvmConnected` alongside SDK and WalletConnect state. +- Wire `connectEvmBtn.onclick`. +- Export `updateConnectEvmConnectionState`. +- Treat the `connect-evm` provider as an active provider that gets: + - `chainChanged` + - `accountsChanged` + - `disconnect`, if supported by the provider +- Avoid duplicating listeners when providers change. + +Provider capability checks are renamed away from install-specific language: + +- `isMetaMaskInstalled()` becomes `isProviderAvailableForDapp()`. +- The check returns true when `globalContext.provider` has a `request` function and either: + - `provider.isMetaMask` is true, or + - the selected provider detail is the `connect-evm` provider, or + - the provider was selected from EIP-6963 or WalletConnect. + +Existing call sites are updated to use `isProviderAvailableForDapp()` without changing the behavior of legacy providers. + +## Data Flow + +Connection flow: + +1. User clicks `Connect EVM`. +2. The handler initializes or reuses the `createEVMClient()` singleton. +3. The handler calls `client.connect({ chainIds })`. +4. The handler retrieves `client.getProvider()`. +5. The handler builds a provider detail: + - `info.uuid`: a stable local identifier, such as `connect-evm` + - `info.name`: `connect-evm` + - `info.icon`: `./sdk-connect.svg` unless a better local asset is added + - `info.rdns`: `io.metamask` + - `provider`: the EIP-1193 provider +6. `setActiveProviderDetail()` installs the provider as `globalContext.provider`. +7. Existing status, account, network, contract, transaction, and signature components continue through `globalContext.provider`. + +Disconnect flow: + +1. User clicks `Connect EVM - Disconnect`. +2. The handler calls `client.disconnect()`. +3. The handler clears accounts, removes the provider detail, updates button state, and emits the same local UI state changes as the other connection paths. +4. If the active provider was `connect-evm`, reset active provider status in the UI without changing other provider entries. + +## Network and Chain Handling + +`getNetworkAndChainId()` continues to call `eth_chainId` first. + +For `net_version`: + +- Existing providers keep their current behavior. +- If `net_version` fails with an unsupported-method error or any error from `connect-evm`, derive the decimal network id from `eth_chainId`. +- Continue calling `handleNewNetwork(networkId)` with a string network id. + +`wallet_switchEthereumChain` is supported by `connect-evm` for configured chains. The network picker uses existing error UI for unsupported chains or wallet rejections. + +## Error Handling + +Button behavior: + +- Disable the `Connect EVM` button while a connection or disconnect is in progress. +- Restore the previous label and style on failure. +- Log connection errors to the console, following the existing dapp pattern. +- Do not clear an already active non-Connect-EVM provider if Connect EVM connection fails. + +Known user-facing errors: + +- `4001`: user rejected the request. +- `-32002`: connection request already pending. +- unsupported chain: show the existing network error path. +- unsupported RPC method: show the requesting component's existing error display. + +Unsupported methods are acceptable for this test dapp as long as they fail locally in the specific test panel and do not break provider initialization or global UI state. + +## Testing + +Automated checks: + +- `yarn lint` +- `yarn build` + +Manual smoke checks: + +- Existing `window.ethereum` startup path still works. +- EIP-6963 providers still render and can become active. +- Existing `Connect` button still calls `eth_requestAccounts`. +- Existing `SDK Connect` button still connects and disconnects. +- Existing `Wallet Connect` button still opens and connects as before. +- New `Connect EVM` button connects, updates accounts, active chain, and active provider display. +- `Connect EVM - Disconnect` clears the Connect EVM session and button state. +- `eth_accounts`, `eth_chainId`, `personal_sign`, and a simple transaction request work when supported by the connected wallet. +- Network picker switches to a configured supported chain. +- Network picker failure for unsupported chains does not break the app. +- A component using `net_version` fallback initializes correctly under Connect EVM. + +## Documentation References + +- MetaMask Connect EVM introduction: https://docs.metamask.io/metamask-connect/evm/ +- MetaMask Connect EVM JavaScript quickstart: https://docs.metamask.io/metamask-connect/evm/quickstart/javascript/ +- Local package source audited for compatibility: `/Users/aphex/repos/metamask/connect-monorepo/packages/connect-evm` + +## Acceptance Criteria + +- The repo depends on the published `@metamask/connect-evm` package. +- The Connect Actions card includes a `Connect EVM` button. +- Clicking `Connect EVM` connects through `@metamask/connect-evm`. +- The provider returned by `client.getProvider()` becomes the app's active provider. +- Existing connection paths continue to work. +- Provider initialization no longer depends on `net_version` succeeding. +- Existing request/signature/transaction panels remain disabled until an active provider and account are available. +- Lint and production build pass. From fcd8f3d5b50950ebe153af80323289d38bbcb82c Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 10:40:47 +0100 Subject: [PATCH 02/21] docs: add connect evm implementation plan --- .../plans/2026-05-28-connect-evm.md | 879 ++++++++++++++++++ 1 file changed, 879 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-28-connect-evm.md diff --git a/docs/superpowers/plans/2026-05-28-connect-evm.md b/docs/superpowers/plans/2026-05-28-connect-evm.md new file mode 100644 index 00000000..35700ac6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-28-connect-evm.md @@ -0,0 +1,879 @@ +# Connect EVM Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a `Connect EVM` button that connects through the published `@metamask/connect-evm` package while preserving the current `window.ethereum`, EIP-6963, MetaMask SDK, and WalletConnect flows. + +**Architecture:** Keep the change additive. Add a small Connect EVM integration module that creates a singleton `createEVMClient()` instance and returns its EIP-1193 provider through the app's existing `globalContext.provider` path. Update provider capability checks so the app no longer requires `provider.isMetaMask` for active provider initialization. + +**Tech Stack:** Yarn v1, webpack, vanilla JavaScript, ethers v5, EIP-1193 provider API, `@metamask/connect-evm`. + +--- + +## File Structure + +- Modify: `package.json` + - Add `@metamask/connect-evm` as a published development dependency to match the existing bundled dapp dependencies. +- Modify: `yarn.lock` + - Updated by `yarn add --dev @metamask/connect-evm`. +- Create: `src/dapp-metadata.js` + - Central owner for dapp metadata shared by legacy SDK/Web3Modal and Connect EVM. +- Modify: `src/connections.js` + - Import `dappMetadata` from the shared module. + - Keep existing SDK and WalletConnect behavior. +- Create: `src/connect-evm.js` + - Owns supported network RPC configuration. + - Owns Connect EVM client singleton creation. + - Exposes `handleConnectEvm(name, button, isConnected)`. +- Modify: `src/components/connections/connections.js` + - Add `Connect EVM` button. +- Modify: `src/index.js` + - Wire `Connect EVM` button. + - Track Connect EVM connection state. + - Rename provider capability checks. + - Add `net_version` fallback. + - Install/remove listeners for any EIP-1193 provider with event methods. + +--- + +### Task 1: Add Published Dependency And Shared Metadata + +**Files:** +- Modify: `package.json` +- Modify: `yarn.lock` +- Create: `src/dapp-metadata.js` +- Modify: `src/connections.js:1-18` + +- [ ] **Step 1: Add the package** + +Run: + +```bash +yarn add --dev @metamask/connect-evm +``` + +Expected: + +- `package.json` has `@metamask/connect-evm` in `devDependencies`. +- `yarn.lock` has entries for `@metamask/connect-evm` and its transitive dependencies. +- Existing dependencies are not removed. + +- [ ] **Step 2: Create shared dapp metadata** + +Create `src/dapp-metadata.js`: + +```javascript +const dappMetadata = { + name: 'E2e Test Dapp', + description: 'This is the E2e Test Dapp', + url: 'https://metamask.github.io/test-dapp/', +}; + +export default dappMetadata; +``` + +- [ ] **Step 3: Update `src/connections.js` to import metadata** + +Replace the top of `src/connections.js` with: + +```javascript +import { MetaMaskSDK } from '@metamask/sdk'; +import globalContext, { + handleNewAccounts, + handleNewProviderDetail, + removeProviderDetail, + setActiveProviderDetail, + updateFormElements, + updateSdkConnectionState, + updateWalletConnectState, +} from '.'; +import dappMetadata from './dapp-metadata'; + +const sdk = new MetaMaskSDK({ dappMetadata }); +``` + +Leave the rest of `src/connections.js` unchanged. + +- [ ] **Step 4: Verify dependency and metadata task** + +Run: + +```bash +yarn lint +``` + +Expected: + +- Exit code `0`. +- No ESLint or Prettier failures. + +- [ ] **Step 5: Commit task 1** + +Run: + +```bash +git add package.json yarn.lock src/dapp-metadata.js src/connections.js +git commit -m "feat: add connect evm dependency" +``` + +Expected: + +- Commit succeeds. + +--- + +### Task 2: Add Connect EVM Integration Module + +**Files:** +- Create: `src/connect-evm.js` + +- [ ] **Step 1: Create the Connect EVM module** + +Create `src/connect-evm.js`: + +```javascript +import { createEVMClient } from '@metamask/connect-evm'; +import globalContext, { + handleNewAccounts, + handleNewProviderDetail, + removeProviderDetail, + setActiveProviderDetail, + updateConnectEvmConnectionState, + updateFormElements, +} from '.'; +import dappMetadata from './dapp-metadata'; + +export const CONNECT_EVM_PROVIDER_NAME = 'connect-evm'; +export const CONNECT_EVM_PROVIDER_UUID = 'connect-evm'; + +export const CONNECT_EVM_SUPPORTED_NETWORKS = { + '0x1': 'https://ethereum.publicnode.com', + '0xaa36a7': 'https://ethereum-sepolia.publicnode.com', + '0xa': 'https://optimism.publicnode.com', + '0x89': 'https://polygon-bor.publicnode.com', + '0x2105': 'https://base.publicnode.com', + '0xa4b1': 'https://arbitrum-one.publicnode.com', + '0xa86a': 'https://avalanche-c-chain-rpc.publicnode.com', + '0x38': 'https://bsc-dataseed.binance.org', + '0x539': 'http://127.0.0.1:8545', + '0x53a': 'http://127.0.0.1:8546', +}; + +export const CONNECT_EVM_CHAIN_IDS = Object.keys( + CONNECT_EVM_SUPPORTED_NETWORKS, +); + +let connectEvmClientPromise; +let connectEvmClient; +let connectEvmProvider; + +async function getConnectEvmClient() { + if (!connectEvmClientPromise) { + connectEvmClientPromise = createEVMClient({ + dapp: dappMetadata, + api: { + supportedNetworks: CONNECT_EVM_SUPPORTED_NETWORKS, + }, + }); + } + + connectEvmClient = await connectEvmClientPromise; + return connectEvmClient; +} + +function getConnectEvmProviderDetail(provider, name) { + return { + info: { + uuid: CONNECT_EVM_PROVIDER_UUID, + name, + icon: './sdk-connect.svg', + rdns: 'io.metamask', + }, + provider, + }; +} + +function setConnectedButtonState(button) { + button.innerText = 'Connect EVM - Disconnect'; + button.classList.remove('btn-primary'); + button.classList.add('btn-danger'); +} + +function setDisconnectedButtonState(button) { + button.innerText = 'Connect EVM'; + button.classList.add('btn-primary'); + button.classList.remove('btn-danger'); +} + +export function isConnectEvmProvider(provider) { + return Boolean(provider && provider === connectEvmProvider); +} + +export async function handleConnectEvm(name, button, isConnected) { + button.disabled = true; + + try { + const client = await getConnectEvmClient(); + + if (isConnected) { + await client.disconnect(); + handleNewAccounts([]); + updateFormElements(); + updateConnectEvmConnectionState(false); + removeProviderDetail(name); + setDisconnectedButtonState(button); + globalContext.connected = false; + return; + } + + const { accounts } = await client.connect({ + chainIds: CONNECT_EVM_CHAIN_IDS, + }); + const provider = client.getProvider(); + connectEvmProvider = provider; + + const providerDetail = getConnectEvmProviderDetail(provider, name); + await setActiveProviderDetail(providerDetail); + handleNewProviderDetail(providerDetail); + updateConnectEvmConnectionState(true); + setConnectedButtonState(button); + updateFormElements(); + handleNewAccounts(accounts); + } catch (err) { + console.error('Error connecting with MetaMask Connect EVM', err); + if (isConnected) { + setConnectedButtonState(button); + } else { + setDisconnectedButtonState(button); + } + } finally { + button.disabled = false; + } +} +``` + +- [ ] **Step 2: Verify module syntax through lint** + +Run: + +```bash +yarn lint:eslint src/connect-evm.js +``` + +Expected: + +- Exit code `0`. +- No ESLint failures. + +- [ ] **Step 3: Commit task 2** + +Run: + +```bash +git add src/connect-evm.js +git commit -m "feat: add connect evm client helper" +``` + +Expected: + +- Commit succeeds. + +--- + +### Task 3: Add The Connect EVM Button + +**Files:** +- Modify: `src/components/connections/connections.js:22-34` + +- [ ] **Step 1: Add the button markup** + +In `src/components/connections/connections.js`, insert the new button after the existing SDK button and before `
`: + +```html + +``` + +The Connect Actions button block should become: + +```javascript + + + +
+``` + +- [ ] **Step 2: Update nearby commented DOM references** + +Update the comment block in `src/components/connections/connections.js` to include the new button: + +```javascript + /* + const onboardButton = document.getElementById('connectButton'); + const walletConnectBtn = document.getElementById('walletConnect'); + const sdkConnectBtn = document.getElementById('sdkConnect'); + const connectEvmBtn = document.getElementById('connectEvm'); + */ +``` + +- [ ] **Step 3: Verify button markup** + +Run: + +```bash +yarn lint:eslint src/components/connections/connections.js +``` + +Expected: + +- Exit code `0`. +- No ESLint failures. + +- [ ] **Step 4: Commit task 3** + +Run: + +```bash +git add src/components/connections/connections.js +git commit -m "feat: add connect evm button" +``` + +Expected: + +- Commit succeeds. + +--- + +### Task 4: Wire Connect EVM Into The Main Page + +**Files:** +- Modify: `src/index.js:3-7` +- Modify: `src/index.js:174-178` +- Modify: `src/index.js:248-278` + +- [ ] **Step 1: Import the Connect EVM handler** + +In `src/index.js`, add `handleConnectEvm` to the connection imports: + +```javascript +import { + handleSdkConnect, + handleWalletConnect, + walletConnect, +} from './connections'; +import { handleConnectEvm } from './connect-evm'; +``` + +- [ ] **Step 2: Read the Connect EVM button** + +In the connection button setup block, add: + +```javascript +const connectEvmBtn = document.getElementById('connectEvm'); +``` + +The block should be: + +```javascript +// Connection buttons set up by this file +const onboardButton = document.getElementById('connectButton'); +const walletConnectBtn = document.getElementById('walletConnect'); +const sdkConnectBtn = document.getElementById('sdkConnect'); +const connectEvmBtn = document.getElementById('connectEvm'); +``` + +- [ ] **Step 3: Track Connect EVM connection state** + +Near the existing state declarations, add: + +```javascript +let isConnectEvmConnected = false; +``` + +The block should become: + +```javascript +const isMetaMaskConnected = () => + globalContext.accounts && globalContext.accounts.length > 0; +let isWalletConnectConnected = false; +let isSdkConnected = false; +let isConnectEvmConnected = false; +``` + +- [ ] **Step 4: Wire the click handler** + +After `sdkConnectBtn.onclick`, add: + +```javascript +connectEvmBtn.onclick = async () => { + await handleConnectEvm( + 'connect-evm', + connectEvmBtn, + isConnectEvmConnected, + ); +}; +``` + +- [ ] **Step 5: Export state updater** + +After `updateSdkConnectionState`, add: + +```javascript +export function updateConnectEvmConnectionState(isConnected) { + isConnectEvmConnected = isConnected; +} +``` + +- [ ] **Step 6: Verify main page wiring** + +Run: + +```bash +yarn lint:eslint src/index.js +``` + +Expected: + +- Exit code `0`. +- No ESLint failures. + +- [ ] **Step 7: Commit task 4** + +Run: + +```bash +git add src/index.js +git commit -m "feat: wire connect evm button" +``` + +Expected: + +- Commit succeeds. + +--- + +### Task 5: Make Provider Initialization EIP-1193 Based + +**Files:** +- Modify: `src/index.js:253-255` +- Modify: `src/index.js:467-483` +- Modify: `src/index.js:506-552` +- Modify: `src/index.js:664-731` + +- [ ] **Step 1: Replace the MetaMask-only provider gate** + +Replace the current `isMetaMaskInstalled` function: + +```javascript +const isMetaMaskInstalled = () => + globalContext.provider && globalContext.provider.isMetaMask; +``` + +with: + +```javascript +const isProviderAvailableForDapp = () => + globalContext.provider && + typeof globalContext.provider.request === 'function'; + +const canSubscribeToProviderEvents = () => + globalContext.provider && typeof globalContext.provider.on === 'function'; + +const removeProviderListener = (eventName, listener) => { + if (typeof globalContext.provider.removeListener === 'function') { + globalContext.provider.removeListener(eventName, listener); + } else if (typeof globalContext.provider.off === 'function') { + globalContext.provider.off(eventName, listener); + } +}; +``` + +- [ ] **Step 2: Add `net_version` fallback** + +Replace `getNetworkAndChainId` with: + +```javascript +const getNetworkAndChainId = async () => { + try { + const chainId = await globalContext.provider.request({ + method: 'eth_chainId', + }); + handleNewChain(chainId); + + let networkId; + try { + networkId = await globalContext.provider.request({ + method: 'net_version', + }); + } catch (err) { + console.warn('Falling back to eth_chainId for network id', err); + networkId = `${parseInt(chainId, 16)}`; + } + handleNewNetwork(networkId); + + handleEIP1559Support(); + } catch (err) { + console.error(err); + } +}; +``` + +- [ ] **Step 3: Update `closeProvider` listener cleanup** + +Replace `closeProvider` with: + +```javascript +const closeProvider = () => { + const previousProvider = globalContext.provider; + + handleNewAccounts([]); + handleNewChain(''); + handleNewNetwork(''); + + if (previousProvider && canSubscribeToProviderEvents()) { + removeProviderListener('chainChanged', handleNewChain); + removeProviderListener('chainChanged', handleEIP1559Support); + removeProviderListener('networkChanged', handleNewNetwork); + removeProviderListener('accountsChanged', handleNewAccounts); + removeProviderListener('accountsChanged', handleEIP1559Support); + } +}; +``` + +- [ ] **Step 4: Update `initializeProvider` listener setup** + +Replace `initializeProvider` with: + +```javascript +const initializeProvider = async () => { + initializeContracts(); + updateFormElements(); + + if (isProviderAvailableForDapp()) { + if ('autoRefreshOnNetworkChange' in globalContext.provider) { + globalContext.provider.autoRefreshOnNetworkChange = false; + } + await getNetworkAndChainId(); + + if (canSubscribeToProviderEvents()) { + globalContext.provider.on('chainChanged', handleNewChain); + globalContext.provider.on('chainChanged', handleEIP1559Support); + globalContext.provider.on('networkChanged', handleNewNetwork); + globalContext.provider.on('accountsChanged', handleNewAccounts); + globalContext.provider.on('accountsChanged', handleEIP1559Support); + } + + try { + const newAccounts = await globalContext.provider.request({ + method: 'eth_accounts', + }); + handleNewAccounts(newAccounts); + } catch (err) { + console.error('Error on init when getting accounts', err); + } + } else { + handleScrollTo(); + } +}; +``` + +- [ ] **Step 5: Update form enablement** + +Replace `updateFormElements` with: + +```javascript +export const updateFormElements = () => { + if (!isProviderAvailableForDapp() || !isMetaMaskConnected()) { + document.dispatchEvent(new Event('disableAndClear')); + } else if (isMetaMaskConnected()) { + globalContext.connected = true; + } + + updateOnboardElements(); + updateContractElements(); +}; +``` + +- [ ] **Step 6: Update onboarding controls** + +Replace `updateOnboardElements` with: + +```javascript +const updateOnboardElements = () => { + let onboarding; + try { + onboarding = new MetaMaskOnboarding({ forwarderOrigin }); + } catch (error) { + console.error(error); + } + + if (!isProviderAvailableForDapp()) { + onboardButton.innerText = 'Click here to install MetaMask!'; + onboardButton.onclick = () => { + onboardButton.innerText = 'Onboarding in progress'; + onboardButton.disabled = true; + onboarding.startOnboarding(); + }; + onboardButton.disabled = false; + return; + } + + document.dispatchEvent(new Event('MetaMaskInstalled')); + + if (isMetaMaskConnected()) { + onboardButton.innerText = 'Connected'; + onboardButton.disabled = true; + if (onboarding) { + onboarding.stopOnboarding(); + } + } else { + onboardButton.innerText = 'Connect'; + onboardButton.onclick = async () => { + try { + const newAccounts = await globalContext.provider.request({ + method: 'eth_requestAccounts', + }); + handleNewAccounts(newAccounts); + } catch (error) { + console.error(error); + } + }; + onboardButton.disabled = false; + } + + if (isWalletConnectConnected) { + if (onboarding) { + onboarding.stopOnboarding(); + } + if ('autoRefreshOnNetworkChange' in globalContext.provider) { + globalContext.provider.autoRefreshOnNetworkChange = false; + } + getNetworkAndChainId(); + + if (canSubscribeToProviderEvents()) { + globalContext.provider.on('chainChanged', handleNewChain); + globalContext.provider.on('chainChanged', handleEIP1559Support); + globalContext.provider.on('chainChanged', handleNewNetwork); + globalContext.provider.on('accountsChanged', handleNewAccounts); + globalContext.provider.on('accountsChanged', handleEIP1559Support); + } + } +}; +``` + +- [ ] **Step 7: Verify provider initialization** + +Run: + +```bash +yarn lint:eslint src/index.js +``` + +Expected: + +- Exit code `0`. +- No ESLint failures. + +- [ ] **Step 8: Commit task 5** + +Run: + +```bash +git add src/index.js +git commit -m "feat: support eip1193 provider initialization" +``` + +Expected: + +- Commit succeeds. + +--- + +### Task 6: Run Build Verification And Fix Integration Issues + +**Files:** +- Modify only files already touched in Tasks 1-5 unless the build output identifies a specific missing browser fallback. + +- [ ] **Step 1: Run full lint** + +Run: + +```bash +yarn lint +``` + +Expected: + +- Exit code `0`. +- No ESLint or Prettier failures. + +- [ ] **Step 2: Run production build** + +Run: + +```bash +yarn build +``` + +Expected: + +- Exit code `0`. +- `dist/main.js` and other build artifacts are emitted. + +- [ ] **Step 3: Inspect changed files** + +Run: + +```bash +git status --short +git diff --stat +``` + +Expected: + +- Source changes match this plan. +- Build artifacts in `dist/` are ignored or intentionally absent from git status. + +- [ ] **Step 4: Commit verification fixes** + +If Tasks 6.1 or 6.2 required source fixes, run: + +```bash +git add package.json yarn.lock src/dapp-metadata.js src/connections.js src/connect-evm.js src/components/connections/connections.js src/index.js +git commit -m "fix: stabilize connect evm integration" +``` + +Expected: + +- Commit succeeds when there are source fixes. +- If there are no source fixes, `git status --short` shows no uncommitted source changes. + +--- + +### Task 7: Manual Smoke Verification + +**Files:** +- No planned source edits. + +- [ ] **Step 1: Start the dev server** + +Run: + +```bash +yarn start +``` + +Expected: + +- webpack-dev-server starts on port `9011`. +- The dapp is available at `http://localhost:9011`. + +- [ ] **Step 2: Verify existing connection paths** + +In a browser, verify: + +- `Use window.ethereum` still appears and can select the injected provider when available. +- EIP-6963 provider cards still render when wallets announce providers. +- `Connect` still calls `eth_requestAccounts` on the active provider. +- `SDK Connect` still connects and toggles to `Sdk Connect - Disconnect`. +- `Wallet Connect` still opens Web3Modal. + +Expected: + +- Existing paths work as they did before this feature. +- No console error appears during initial page load. + +- [ ] **Step 3: Verify Connect EVM path** + +In a browser, verify: + +- `Connect EVM` button appears in the Connect Actions card. +- Clicking `Connect EVM` initiates a MetaMask Connect EVM connection. +- Successful connection changes the button text to `Connect EVM - Disconnect`. +- Active Provider shows: + - UUID: `connect-evm` + - Name: `connect-evm` + - Icon: local SDK icon +- Status shows non-empty Accounts. +- Status shows a ChainId. + +Expected: + +- Existing transaction/signature panels become enabled after accounts are present. +- The console does not show `net_version` as an uncaught initialization failure. + +- [ ] **Step 4: Verify core EIP-1193 requests** + +Use the page controls: + +- Click `eth_accounts`. +- Click a personal sign action. +- Use the network picker to switch to Sepolia or another configured supported chain. + +Expected: + +- `eth_accounts` returns the connected account. +- Personal sign opens a wallet request or returns a user-rejection error when rejected. +- Network switching either succeeds or shows the existing network error message without breaking the page. + +- [ ] **Step 5: Stop the dev server** + +Stop the `yarn start` process with `Ctrl-C`. + +Expected: + +- Dev server exits cleanly. + +--- + +### Task 8: Final Review + +**Files:** +- No planned source edits. + +- [ ] **Step 1: Review final diff** + +Run: + +```bash +git log --oneline -8 +git status --short +``` + +Expected: + +- Recent commits correspond to this plan's tasks. +- `git status --short` has no uncommitted source changes. + +- [ ] **Step 2: Summarize implementation** + +Prepare a final summary containing: + +- Dependency added: `@metamask/connect-evm`. +- New button: `Connect EVM`. +- New module: `src/connect-evm.js`. +- Provider initialization change: EIP-1193 `request` capability replaces the old `isMetaMask` gate. +- Verification run: `yarn lint`, `yarn build`, and manual smoke checks. + +Expected: + +- The summary is short and includes any verification that was not run. From 66e439477c4535c2d56166cffa7b3c4b814dda15 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:18:25 +0100 Subject: [PATCH 03/21] chore: ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b10648c1..28ad1a70 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules dist .eslintcache .DS_Store +.worktrees From eb73c471875b1890f7ef9fb7567607acb05b0d12 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:22:26 +0100 Subject: [PATCH 04/21] chore: fix connect evm plan baseline lint --- .eslintrc.js | 1 + .../plans/2026-05-28-connect-evm.md | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 694e34be..a078a7f1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,6 +19,7 @@ module.exports = { { files: ['src/**/*.js'], parserOptions: { + ecmaVersion: 2020, sourceType: 'module', }, }, diff --git a/docs/superpowers/plans/2026-05-28-connect-evm.md b/docs/superpowers/plans/2026-05-28-connect-evm.md index 35700ac6..cf3ec761 100644 --- a/docs/superpowers/plans/2026-05-28-connect-evm.md +++ b/docs/superpowers/plans/2026-05-28-connect-evm.md @@ -39,6 +39,7 @@ ### Task 1: Add Published Dependency And Shared Metadata **Files:** + - Modify: `package.json` - Modify: `yarn.lock` - Create: `src/dapp-metadata.js` @@ -125,6 +126,7 @@ Expected: ### Task 2: Add Connect EVM Integration Module **Files:** + - Create: `src/connect-evm.js` - [ ] **Step 1: Create the Connect EVM module** @@ -283,6 +285,7 @@ Expected: ### Task 3: Add The Connect EVM Button **Files:** + - Modify: `src/components/connections/connections.js:22-34` - [ ] **Step 1: Add the button markup** @@ -290,12 +293,9 @@ Expected: In `src/components/connections/connections.js`, insert the new button after the existing SDK button and before `
`: ```html - + ``` The Connect Actions button block should become: @@ -327,7 +327,7 @@ The Connect Actions button block should become: Update the comment block in `src/components/connections/connections.js` to include the new button: ```javascript - /* +/* const onboardButton = document.getElementById('connectButton'); const walletConnectBtn = document.getElementById('walletConnect'); const sdkConnectBtn = document.getElementById('sdkConnect'); @@ -366,6 +366,7 @@ Expected: ### Task 4: Wire Connect EVM Into The Main Page **Files:** + - Modify: `src/index.js:3-7` - Modify: `src/index.js:174-178` - Modify: `src/index.js:248-278` @@ -425,11 +426,7 @@ After `sdkConnectBtn.onclick`, add: ```javascript connectEvmBtn.onclick = async () => { - await handleConnectEvm( - 'connect-evm', - connectEvmBtn, - isConnectEvmConnected, - ); + await handleConnectEvm('connect-evm', connectEvmBtn, isConnectEvmConnected); }; ``` @@ -474,6 +471,7 @@ Expected: ### Task 5: Make Provider Initialization EIP-1193 Based **Files:** + - Modify: `src/index.js:253-255` - Modify: `src/index.js:467-483` - Modify: `src/index.js:506-552` @@ -711,6 +709,7 @@ Expected: ### Task 6: Run Build Verification And Fix Integration Issues **Files:** + - Modify only files already touched in Tasks 1-5 unless the build output identifies a specific missing browser fallback. - [ ] **Step 1: Run full lint** @@ -772,6 +771,7 @@ Expected: ### Task 7: Manual Smoke Verification **Files:** + - No planned source edits. - [ ] **Step 1: Start the dev server** @@ -848,6 +848,7 @@ Expected: ### Task 8: Final Review **Files:** + - No planned source edits. - [ ] **Step 1: Review final diff** From 420d1d2d533d7040249d0834cd430db9726a991b Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:25:32 +0100 Subject: [PATCH 05/21] feat: add connect evm dependency --- package.json | 1 + src/connections.js | 7 +- src/dapp-metadata.js | 7 ++ yarn.lock | 253 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 src/dapp-metadata.js diff --git a/package.json b/package.json index c5053de1..50814ae2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@lavamoat/allow-scripts": "^2.5.1", "@lavamoat/preinstall-always-fail": "^2.0.0", "@metamask/auto-changelog": "^2.5.0", + "@metamask/connect-evm": "^1.4.0", "@metamask/eslint-config": "^6.0.0", "@metamask/eslint-config-nodejs": "^6.0.0", "@metamask/eth-sig-util": "^7.0.1", diff --git a/src/connections.js b/src/connections.js index c126d989..61c40c35 100644 --- a/src/connections.js +++ b/src/connections.js @@ -1,4 +1,5 @@ import { MetaMaskSDK } from '@metamask/sdk'; +import dappMetadata from './dapp-metadata'; import globalContext, { handleNewAccounts, handleNewProviderDetail, @@ -9,12 +10,6 @@ import globalContext, { updateWalletConnectState, } from '.'; -const dappMetadata = { - name: 'E2e Test Dapp', - description: 'This is the E2e Test Dapp', - url: 'https://metamask.github.io/test-dapp/', -}; - const sdk = new MetaMaskSDK({ dappMetadata }); export const initializeWeb3Modal = () => { diff --git a/src/dapp-metadata.js b/src/dapp-metadata.js new file mode 100644 index 00000000..0b50f971 --- /dev/null +++ b/src/dapp-metadata.js @@ -0,0 +1,7 @@ +const dappMetadata = { + name: 'E2e Test Dapp', + description: 'This is the E2e Test Dapp', + url: 'https://metamask.github.io/test-dapp/', +}; + +export default dappMetadata; diff --git a/yarn.lock b/yarn.lock index 505f325b..3c649fc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -837,6 +837,13 @@ "@metamask/superstruct" "^3.1.0" "@metamask/utils" "^9.0.0" +"@metamask/analytics@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@metamask/analytics/-/analytics-0.6.0.tgz#2c04f26f1a467e88e38fa56e53fb9acc6507ca35" + integrity sha512-L250lV3PANTcKqhZkmRV1Obkyddm6JDQvDBuUVAi+7TmPdOVdpavWNXyEh7ErscjwG0z2wiF9Ne0ihK/nCdX0w== + dependencies: + openapi-fetch "^0.13.5" + "@metamask/auto-changelog@^2.5.0": version "2.6.1" resolved "https://registry.yarnpkg.com/@metamask/auto-changelog/-/auto-changelog-2.6.1.tgz#5a6291df6c1592f010bd54f1a97814a4570b1eaf" @@ -847,6 +854,38 @@ semver "^7.3.5" yargs "^17.0.1" +"@metamask/connect-evm@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@metamask/connect-evm/-/connect-evm-1.4.0.tgz#7e524b718436f6fdd499461fc49c4586a9399158" + integrity sha512-SX3GDh6UOTFaVDCLU9V5a+UTymz7avxuisRb4wedI/mwfE768XYq3TvLVXVTf52DeeIFmO5pRh8ShabsOsfF5w== + dependencies: + "@metamask/analytics" "^0.6.0" + "@metamask/connect-multichain" "^0.15.0" + "@metamask/utils" "^11.8.1" + +"@metamask/connect-multichain@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@metamask/connect-multichain/-/connect-multichain-0.15.0.tgz#0d15b7bf4a5913d24787e4c1db13818c54330646" + integrity sha512-ufLG7nvmUTHv+xLojCD2jS4tB/fJ2z78k6njGycO3kfsnJSvJhG97mUV/J7iU23Ojfk2tT3obwbhR8QRM16pXg== + dependencies: + "@metamask/analytics" "^0.6.0" + "@metamask/mobile-wallet-protocol-core" "^0.4.0" + "@metamask/mobile-wallet-protocol-dapp-client" "^0.3.0" + "@metamask/multichain-api-client" "^0.10.1" + "@metamask/multichain-ui" "^0.4.1" + "@metamask/onboarding" "^1.0.1" + "@metamask/rpc-errors" "^7.0.3" + "@metamask/utils" "^11.8.1" + "@paulmillr/qr" "^0.2.1" + bowser "^2.11.0" + buffer "^6.0.3" + cross-fetch "^4.1.0" + eciesjs "0.4.17" + eventemitter3 "^5.0.1" + pako "^2.1.0" + uuid "^11.1.0" + ws "^8.18.3" + "@metamask/eslint-config-nodejs@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@metamask/eslint-config-nodejs/-/eslint-config-nodejs-6.0.0.tgz#df77bb35b91556030f1b23ad4ff51c1caf033339" @@ -888,6 +927,38 @@ "@metamask/utils" "^8.3.0" readable-stream "^3.6.2" +"@metamask/mobile-wallet-protocol-core@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@metamask/mobile-wallet-protocol-core/-/mobile-wallet-protocol-core-0.4.0.tgz#05ef73ed3a70f7c96f0cba4e461c461e7357317a" + integrity sha512-rB1wMogvSUsFaxyH/eVUCczIkTxVaPPETlD/wgm+gw7EbWP0LlZPY7Bh+DICSfUCJ0zqnoFuwr77WNJvZ6ZiWw== + dependencies: + async-mutex "^0.5.0" + centrifuge "^5.3.5" + eventemitter3 "^5.0.1" + uuid "^11.1.0" + +"@metamask/mobile-wallet-protocol-dapp-client@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@metamask/mobile-wallet-protocol-dapp-client/-/mobile-wallet-protocol-dapp-client-0.3.0.tgz#0fbb5757219edaaaa51ca52e1c9ed91f8278c282" + integrity sha512-rXStrvIa57a8OaeM+3HeR6Z9ETHOvmQi/9s6CLplDwH2hn2MWjI6WW3EUrxq2KGmGuhbO5Oo21ANnD23QKfduw== + dependencies: + "@metamask/mobile-wallet-protocol-core" "^0.4.0" + "@metamask/utils" "^9.1.0" + uuid "^11.1.0" + +"@metamask/multichain-api-client@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@metamask/multichain-api-client/-/multichain-api-client-0.10.1.tgz#b5f891a2c9783b8ebb9f097a04c89939b790545a" + integrity sha512-LsqO2SiDcTgOuXyVYEB0zgBaVNhryhP2tYI3L7tLa7PoeDqMkNIreFhDeu8jM5tPWkCimQvMwCkG3DF4P5dD3A== + +"@metamask/multichain-ui@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@metamask/multichain-ui/-/multichain-ui-0.4.1.tgz#cc832e12a0507d5153bb9401ae9aa6665b7d8b16" + integrity sha512-tJgTot8Pfkda895A6biJu7rE+jlQdVCNVzGgW+2wM9aFG20G+GEbQy3KO7uC4ImUvaKV4SyJ45r6Ir/Yf55mqw== + dependencies: + "@paulmillr/qr" "^0.2.1" + qr-code-styling "^1.9.2" + "@metamask/object-multiplex@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz#5e2e908fc46aee581cbba809870eeee0e571cbb6" @@ -929,6 +1000,14 @@ "@metamask/utils" "^9.0.0" fast-safe-stringify "^2.0.6" +"@metamask/rpc-errors@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-7.0.3.tgz#68fe7d1dcb7913c7e89ec48f549e2ab498ecff14" + integrity sha512-nrEaeBawm8yFU7hetJKok/CUs0tQsWtTqp3OLbFhPUMXYqU7uI5LAV5vi9o7rTjFkUyof7Nzbw5bea5+1ou+dg== + dependencies: + "@metamask/utils" "^11.4.2" + fast-safe-stringify "^2.0.6" + "@metamask/safe-event-emitter@2.0.0", "@metamask/safe-event-emitter@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" @@ -996,6 +1075,23 @@ resolved "https://registry.yarnpkg.com/@metamask/superstruct/-/superstruct-3.2.1.tgz#fca933017c5b78529f8f525560cef32c57e889d2" integrity sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g== +"@metamask/utils@^11.4.2", "@metamask/utils@^11.8.1": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.11.0.tgz#6675946df767da6cbe306c7b5e76685320a6c826" + integrity sha512-0nF2CWjWQr/m0Y2t2lJnBTU1/CZPPTvKvcESLplyWe/tyeb8zFOi/FeneDmaFnML6LYRIGZU6f+xR0jKAIUZfw== + dependencies: + "@ethereumjs/tx" "^4.2.0" + "@metamask/superstruct" "^3.1.0" + "@noble/hashes" "^1.3.1" + "@scure/base" "^1.1.3" + "@types/debug" "^4.1.7" + "@types/lodash" "^4.17.20" + debug "^4.3.4" + lodash "^4.17.21" + pony-cause "^2.1.10" + semver "^7.5.4" + uuid "^9.0.1" + "@metamask/utils@^3.0.1": version "3.6.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.6.0.tgz#b218b969a05ca7a8093b5d1670f6625061de707d" @@ -1021,7 +1117,7 @@ semver "^7.5.4" uuid "^9.0.1" -"@metamask/utils@^9.0.0": +"@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-9.3.0.tgz#4726bd7f5d6a43ea8425b6d663ab9207f617c2d1" integrity sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g== @@ -1216,6 +1312,58 @@ resolved "https://registry.yarnpkg.com/@paulmillr/qr/-/qr-0.2.1.tgz#76ade7080be4ac4824f638146fd8b6db1805eeca" integrity sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.5.tgz#d9315ad7cf3f30aac70bda3c068443dc6f143659" + integrity sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g== + +"@protobufjs/eventemitter@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz#d512cb26c0ae026091ee2c1167f1be6faf5c842a" + integrity sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg== + +"@protobufjs/fetch@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.1.tgz#4d6fc00c8fb64016a5c81b469d549046350f1065" + integrity sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.2.tgz#ae64fbc014ff44c8bfad03dd4c93cd2d6a4c82db" + integrity sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.1.tgz#eaee5900122c110a3dbcb728c0597014a2621774" + integrity sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -1559,6 +1707,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.17.20": + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f" + integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ== + "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -1590,6 +1743,13 @@ dependencies: undici-types "~7.19.0" +"@types/node@>=13.7.0": + version "25.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.1.tgz#3bda556db500ae4319c08e7fc9ab94f19013ba0b" + integrity sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg== + dependencies: + undici-types ">=7.24.0 <7.24.7" + "@types/node@^12.12.54": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -2570,6 +2730,13 @@ async-mutex@^0.2.6: dependencies: tslib "^2.0.0" +async-mutex@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== + dependencies: + tslib "^2.4.0" + async@^3.2.4: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" @@ -2699,7 +2866,7 @@ borsh@^0.7.0: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" -bowser@^2.9.0: +bowser@^2.11.0, bowser@^2.9.0: version "2.14.1" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.14.1.tgz#4ea39bf31e305184522d7ad7bfd91389e4f0cb79" integrity sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg== @@ -2872,6 +3039,14 @@ caniuse-lite@^1.0.30001782: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz#31e97d1bfec332b3f2d7eea7781460c97629b3bf" integrity sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ== +centrifuge@^5.3.5: + version "5.6.0" + resolved "https://registry.yarnpkg.com/centrifuge/-/centrifuge-5.6.0.tgz#3dfc69ad549d3e2aafc2f44c39087af9ddc36ec1" + integrity sha512-Cs0uYnlngtxbsm8lwKXb5PCD3fhgVDRyqEaAtXEp0BMmP4pE/tcwFWCFxcYy6b2faChjmsrF9IgA/G44OSqu/w== + dependencies: + events "^3.3.0" + protobufjs "^7.6.0" + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3161,7 +3336,7 @@ cross-fetch@^3.1.4: dependencies: node-fetch "^2.7.0" -cross-fetch@^4.0.0: +cross-fetch@^4.0.0, cross-fetch@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.1.0.tgz#8f69355007ee182e47fa692ecbaa37a52e43c3d2" integrity sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw== @@ -3416,6 +3591,16 @@ duplexify@^4.1.2: readable-stream "^3.1.1" stream-shift "^1.0.2" +eciesjs@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.17.tgz#5aca58bf8c794ec6cf971ce51b10c8d21292189a" + integrity sha512-TOOURki4G7sD1wDCjj7NfLaXZZ49dFOeEb5y39IXpb8p0hRzVvfvzZHOi5JcT+PpyAbi/Y+lxPb8eTag2WYH8w== + dependencies: + "@ecies/ciphers" "^0.2.5" + "@noble/ciphers" "^1.3.0" + "@noble/curves" "^1.9.7" + "@noble/hashes" "^1.8.0" + eciesjs@^0.4.11: version "0.4.18" resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.18.tgz#5f1a40b2171a1fdd97854bdfc31620bb8a1dbbbe" @@ -5343,6 +5528,16 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== +lodash@^4.17.21: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + +long@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + lru-cache@^11.2.7: version "11.3.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.5.tgz#29047d348c0b2793e3112a01c739bb7c6d855637" @@ -5952,6 +6147,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6169,6 +6369,24 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +protobufjs@^7.6.0: + version "7.6.1" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.6.1.tgz#6320bb08c3be7dcfc6f9193ee03d3a4643f1eb37" + integrity sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.5" + "@protobufjs/eventemitter" "^1.1.1" + "@protobufjs/fetch" "^1.1.1" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.2" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.1" + "@types/node" ">=13.7.0" + long "^5.3.2" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -6195,6 +6413,18 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +qr-code-styling@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/qr-code-styling/-/qr-code-styling-1.9.2.tgz#071714860a7e59829e8822c9575e989042139d2d" + integrity sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg== + dependencies: + qrcode-generator "^1.4.4" + +qrcode-generator@^1.4.4: + version "1.5.2" + resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.5.2.tgz#43faea8061a60d2b4ca5e9b0c0c0fb99e2d93e3a" + integrity sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw== + qrcode@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" @@ -7216,7 +7446,7 @@ tslib@1.14.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.3.1, tslib@^2.6.0, tslib@^2.8.0: +tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.6.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -7318,6 +7548,11 @@ uncrypto@^0.1.3: resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b" integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q== +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== + undici-types@~7.19.0: version "7.19.2" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" @@ -7426,6 +7661,11 @@ uuid@^11.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== +uuid@^11.1.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.1.tgz#f6d81d2e1c65d00762e5e29b16c5d2d995e208ad" + integrity sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -7762,6 +8002,11 @@ ws@^8.13.0, ws@^8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.0.tgz#4cd9532358eba60bc863aad1623dfb045a4d4af8" integrity sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA== +ws@^8.18.3: + version "8.21.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951" + integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g== + ws@~8.18.3: version "8.18.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" From e32cae287b9297ae024fd6d2130be3518822de9c Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:29:06 +0100 Subject: [PATCH 06/21] chore: require node 20 --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb185b4a..a44155e0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ It can be used by navigating to `/request.html?method=${METHOD}¶ms=${PARAMS} ### Setup -- Install [Node.js](https://nodejs.org) version 16 +- Install [Node.js](https://nodejs.org) version 20.19.0 or later - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you. - Install [Yarn v1](https://yarnpkg.com/en/docs/install) - Run `yarn setup` to install dependencies and run any required post-install scripts diff --git a/package.json b/package.json index 50814ae2..a67f3e62 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "9.9.0", "description": "A simple dapp used in MetaMask e2e tests.", "engines": { - "node": ">= 18.0.0" + "node": ">=20.19.0" }, "scripts": { "setup": "yarn install && yarn allow-scripts", From 7aab37687a19a5628fac8eaaf1b3f470a8687833 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:31:09 +0100 Subject: [PATCH 07/21] chore: update node version file --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 3f430af8..f62f0b29 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v20.19.1 From 846fda3d48a99d8cbd9df0bc728915945f2b4a75 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:36:24 +0100 Subject: [PATCH 08/21] feat: add connect evm client helper --- src/connect-evm.js | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/connect-evm.js diff --git a/src/connect-evm.js b/src/connect-evm.js new file mode 100644 index 00000000..52e7894f --- /dev/null +++ b/src/connect-evm.js @@ -0,0 +1,120 @@ +import { createEVMClient } from '@metamask/connect-evm'; +import dappMetadata from './dapp-metadata'; +/* eslint-disable import/named */ +import globalContext, { + handleNewAccounts, + handleNewProviderDetail, + removeProviderDetail, + setActiveProviderDetail, + updateConnectEvmConnectionState, + updateFormElements, +} from '.'; +/* eslint-enable import/named */ + +export const CONNECT_EVM_PROVIDER_NAME = 'connect-evm'; +export const CONNECT_EVM_PROVIDER_UUID = 'connect-evm'; + +export const CONNECT_EVM_SUPPORTED_NETWORKS = { + '0x1': 'https://ethereum.publicnode.com', + '0xaa36a7': 'https://ethereum-sepolia.publicnode.com', + '0xa': 'https://optimism.publicnode.com', + '0x89': 'https://polygon-bor.publicnode.com', + '0x2105': 'https://base.publicnode.com', + '0xa4b1': 'https://arbitrum-one.publicnode.com', + '0xa86a': 'https://avalanche-c-chain-rpc.publicnode.com', + '0x38': 'https://bsc-dataseed.binance.org', + '0x539': 'http://127.0.0.1:8545', + '0x53a': 'http://127.0.0.1:8546', +}; + +export const CONNECT_EVM_CHAIN_IDS = Object.keys( + CONNECT_EVM_SUPPORTED_NETWORKS, +); + +let connectEvmClientPromise; +let connectEvmClient; +let connectEvmProvider; + +async function getConnectEvmClient() { + if (!connectEvmClientPromise) { + connectEvmClientPromise = createEVMClient({ + dapp: dappMetadata, + api: { + supportedNetworks: CONNECT_EVM_SUPPORTED_NETWORKS, + }, + }); + } + + connectEvmClient = await connectEvmClientPromise; + return connectEvmClient; +} + +function getConnectEvmProviderDetail(provider, name) { + return { + info: { + uuid: CONNECT_EVM_PROVIDER_UUID, + name, + icon: './sdk-connect.svg', + rdns: 'io.metamask', + }, + provider, + }; +} + +function setConnectedButtonState(button) { + button.innerText = 'Connect EVM - Disconnect'; + button.classList.remove('btn-primary'); + button.classList.add('btn-danger'); +} + +function setDisconnectedButtonState(button) { + button.innerText = 'Connect EVM'; + button.classList.add('btn-primary'); + button.classList.remove('btn-danger'); +} + +export function isConnectEvmProvider(provider) { + return Boolean(provider && provider === connectEvmProvider); +} + +export async function handleConnectEvm(name, button, isConnected) { + button.disabled = true; + + try { + const client = await getConnectEvmClient(); + + if (isConnected) { + await client.disconnect(); + handleNewAccounts([]); + updateFormElements(); + updateConnectEvmConnectionState(false); + removeProviderDetail(name); + setDisconnectedButtonState(button); + globalContext.connected = false; + return; + } + + const { accounts } = await client.connect({ + chainIds: CONNECT_EVM_CHAIN_IDS, + }); + const provider = client.getProvider(); + connectEvmProvider = provider; + + const providerDetail = getConnectEvmProviderDetail(provider, name); + await setActiveProviderDetail(providerDetail); + handleNewProviderDetail(providerDetail); + updateConnectEvmConnectionState(true); + setConnectedButtonState(button); + updateFormElements(); + handleNewAccounts(accounts); + } catch (err) { + console.error('Error connecting with MetaMask Connect EVM', err); + if (isConnected) { + setConnectedButtonState(button); + } else { + setDisconnectedButtonState(button); + } + } finally { + button.disabled = false; + } +} From f61acc641770ffd50273e2152d406cf8d17140d7 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:40:41 +0100 Subject: [PATCH 09/21] fix: harden connect evm helper --- src/connect-evm.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/connect-evm.js b/src/connect-evm.js index 52e7894f..3cb4e247 100644 --- a/src/connect-evm.js +++ b/src/connect-evm.js @@ -1,15 +1,12 @@ import { createEVMClient } from '@metamask/connect-evm'; import dappMetadata from './dapp-metadata'; -/* eslint-disable import/named */ import globalContext, { handleNewAccounts, handleNewProviderDetail, removeProviderDetail, setActiveProviderDetail, - updateConnectEvmConnectionState, updateFormElements, } from '.'; -/* eslint-enable import/named */ export const CONNECT_EVM_PROVIDER_NAME = 'connect-evm'; export const CONNECT_EVM_PROVIDER_UUID = 'connect-evm'; @@ -18,7 +15,7 @@ export const CONNECT_EVM_SUPPORTED_NETWORKS = { '0x1': 'https://ethereum.publicnode.com', '0xaa36a7': 'https://ethereum-sepolia.publicnode.com', '0xa': 'https://optimism.publicnode.com', - '0x89': 'https://polygon-bor.publicnode.com', + '0x89': 'https://polygon-bor-rpc.publicnode.com', '0x2105': 'https://base.publicnode.com', '0xa4b1': 'https://arbitrum-one.publicnode.com', '0xa86a': 'https://avalanche-c-chain-rpc.publicnode.com', @@ -35,6 +32,8 @@ let connectEvmClientPromise; let connectEvmClient; let connectEvmProvider; +const noop = () => undefined; + async function getConnectEvmClient() { if (!connectEvmClientPromise) { connectEvmClientPromise = createEVMClient({ @@ -77,7 +76,12 @@ export function isConnectEvmProvider(provider) { return Boolean(provider && provider === connectEvmProvider); } -export async function handleConnectEvm(name, button, isConnected) { +export async function handleConnectEvm( + name, + button, + isConnected, + updateConnectionState = noop, +) { button.disabled = true; try { @@ -87,7 +91,7 @@ export async function handleConnectEvm(name, button, isConnected) { await client.disconnect(); handleNewAccounts([]); updateFormElements(); - updateConnectEvmConnectionState(false); + updateConnectionState(false); removeProviderDetail(name); setDisconnectedButtonState(button); globalContext.connected = false; @@ -103,10 +107,11 @@ export async function handleConnectEvm(name, button, isConnected) { const providerDetail = getConnectEvmProviderDetail(provider, name); await setActiveProviderDetail(providerDetail); handleNewProviderDetail(providerDetail); - updateConnectEvmConnectionState(true); + updateConnectionState(true); setConnectedButtonState(button); updateFormElements(); handleNewAccounts(accounts); + globalContext.connected = true; } catch (err) { console.error('Error connecting with MetaMask Connect EVM', err); if (isConnected) { From 885eb5efafbdb6df8d5c59252d479ed47a197b84 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:43:30 +0100 Subject: [PATCH 10/21] fix: allow connect evm init retry --- src/connect-evm.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/connect-evm.js b/src/connect-evm.js index 3cb4e247..26fe9c9d 100644 --- a/src/connect-evm.js +++ b/src/connect-evm.js @@ -44,8 +44,13 @@ async function getConnectEvmClient() { }); } - connectEvmClient = await connectEvmClientPromise; - return connectEvmClient; + try { + connectEvmClient = await connectEvmClientPromise; + return connectEvmClient; + } catch (err) { + connectEvmClientPromise = undefined; + throw err; + } } function getConnectEvmProviderDetail(provider, name) { From a2b6a36f0c356a02f5dced02b64fc2440f0ee5bf Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 11:46:34 +0100 Subject: [PATCH 11/21] feat: add connect evm button --- src/components/connections/connections.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/connections/connections.js b/src/components/connections/connections.js index 431b20f5..471d5600 100644 --- a/src/components/connections/connections.js +++ b/src/components/connections/connections.js @@ -31,6 +31,12 @@ export function connectionsComponent(parentContainer) { > SDK Connect +
+ >Connect EVM
-``` - -The Connect Actions button block should become: - -```javascript - - - -
-``` - -- [ ] **Step 2: Update nearby commented DOM references** - -Update the comment block in `src/components/connections/connections.js` to include the new button: - -```javascript -/* - const onboardButton = document.getElementById('connectButton'); - const walletConnectBtn = document.getElementById('walletConnect'); - const sdkConnectBtn = document.getElementById('sdkConnect'); - const connectEvmBtn = document.getElementById('connectEvm'); - */ -``` - -- [ ] **Step 3: Verify button markup** - -Run: - -```bash -yarn lint:eslint src/components/connections/connections.js -``` - -Expected: - -- Exit code `0`. -- No ESLint failures. - -- [ ] **Step 4: Commit task 3** - -Run: - -```bash -git add src/components/connections/connections.js -git commit -m "feat: add connect evm button" -``` - -Expected: - -- Commit succeeds. - ---- - -### Task 4: Wire Connect EVM Into The Main Page - -**Files:** - -- Modify: `src/index.js:3-7` -- Modify: `src/index.js:174-178` -- Modify: `src/index.js:248-278` - -- [ ] **Step 1: Import the Connect EVM handler** - -In `src/index.js`, add `handleConnectEvm` to the connection imports: - -```javascript -import { - handleSdkConnect, - handleWalletConnect, - walletConnect, -} from './connections'; -import { handleConnectEvm } from './connect-evm'; -``` - -- [ ] **Step 2: Read the Connect EVM button** - -In the connection button setup block, add: - -```javascript -const connectEvmBtn = document.getElementById('connectEvm'); -``` - -The block should be: - -```javascript -// Connection buttons set up by this file -const onboardButton = document.getElementById('connectButton'); -const walletConnectBtn = document.getElementById('walletConnect'); -const sdkConnectBtn = document.getElementById('sdkConnect'); -const connectEvmBtn = document.getElementById('connectEvm'); -``` - -- [ ] **Step 3: Track Connect EVM connection state** - -Near the existing state declarations, add: - -```javascript -let isConnectEvmConnected = false; -``` - -The block should become: - -```javascript -const isMetaMaskConnected = () => - globalContext.accounts && globalContext.accounts.length > 0; -let isWalletConnectConnected = false; -let isSdkConnected = false; -let isConnectEvmConnected = false; -``` - -- [ ] **Step 4: Wire the click handler** - -After `sdkConnectBtn.onclick`, add: - -```javascript -connectEvmBtn.onclick = async () => { - await handleConnectEvm('connect-evm', connectEvmBtn, isConnectEvmConnected); -}; -``` - -- [ ] **Step 5: Export state updater** - -After `updateSdkConnectionState`, add: - -```javascript -export function updateConnectEvmConnectionState(isConnected) { - isConnectEvmConnected = isConnected; -} -``` - -- [ ] **Step 6: Verify main page wiring** - -Run: - -```bash -yarn lint:eslint src/index.js -``` - -Expected: - -- Exit code `0`. -- No ESLint failures. - -- [ ] **Step 7: Commit task 4** - -Run: - -```bash -git add src/index.js -git commit -m "feat: wire connect evm button" -``` - -Expected: - -- Commit succeeds. - ---- - -### Task 5: Make Provider Initialization EIP-1193 Based - -**Files:** - -- Modify: `src/index.js:253-255` -- Modify: `src/index.js:467-483` -- Modify: `src/index.js:506-552` -- Modify: `src/index.js:664-731` - -- [ ] **Step 1: Replace the MetaMask-only provider gate** - -Replace the current `isMetaMaskInstalled` function: - -```javascript -const isMetaMaskInstalled = () => - globalContext.provider && globalContext.provider.isMetaMask; -``` - -with: - -```javascript -const isProviderAvailableForDapp = () => - globalContext.provider && - typeof globalContext.provider.request === 'function'; - -const canSubscribeToProviderEvents = () => - globalContext.provider && typeof globalContext.provider.on === 'function'; - -const removeProviderListener = (eventName, listener) => { - if (typeof globalContext.provider.removeListener === 'function') { - globalContext.provider.removeListener(eventName, listener); - } else if (typeof globalContext.provider.off === 'function') { - globalContext.provider.off(eventName, listener); - } -}; -``` - -- [ ] **Step 2: Add `net_version` fallback** - -Replace `getNetworkAndChainId` with: - -```javascript -const getNetworkAndChainId = async () => { - try { - const chainId = await globalContext.provider.request({ - method: 'eth_chainId', - }); - handleNewChain(chainId); - - let networkId; - try { - networkId = await globalContext.provider.request({ - method: 'net_version', - }); - } catch (err) { - console.warn('Falling back to eth_chainId for network id', err); - networkId = `${parseInt(chainId, 16)}`; - } - handleNewNetwork(networkId); - - handleEIP1559Support(); - } catch (err) { - console.error(err); - } -}; -``` - -- [ ] **Step 3: Update `closeProvider` listener cleanup** - -Replace `closeProvider` with: - -```javascript -const closeProvider = () => { - const previousProvider = globalContext.provider; - - handleNewAccounts([]); - handleNewChain(''); - handleNewNetwork(''); - - if (previousProvider && canSubscribeToProviderEvents()) { - removeProviderListener('chainChanged', handleNewChain); - removeProviderListener('chainChanged', handleEIP1559Support); - removeProviderListener('networkChanged', handleNewNetwork); - removeProviderListener('accountsChanged', handleNewAccounts); - removeProviderListener('accountsChanged', handleEIP1559Support); - } -}; -``` - -- [ ] **Step 4: Update `initializeProvider` listener setup** - -Replace `initializeProvider` with: - -```javascript -const initializeProvider = async () => { - initializeContracts(); - updateFormElements(); - - if (isProviderAvailableForDapp()) { - if ('autoRefreshOnNetworkChange' in globalContext.provider) { - globalContext.provider.autoRefreshOnNetworkChange = false; - } - await getNetworkAndChainId(); - - if (canSubscribeToProviderEvents()) { - globalContext.provider.on('chainChanged', handleNewChain); - globalContext.provider.on('chainChanged', handleEIP1559Support); - globalContext.provider.on('networkChanged', handleNewNetwork); - globalContext.provider.on('accountsChanged', handleNewAccounts); - globalContext.provider.on('accountsChanged', handleEIP1559Support); - } - - try { - const newAccounts = await globalContext.provider.request({ - method: 'eth_accounts', - }); - handleNewAccounts(newAccounts); - } catch (err) { - console.error('Error on init when getting accounts', err); - } - } else { - handleScrollTo(); - } -}; -``` - -- [ ] **Step 5: Update form enablement** - -Replace `updateFormElements` with: - -```javascript -export const updateFormElements = () => { - if (!isProviderAvailableForDapp() || !isMetaMaskConnected()) { - document.dispatchEvent(new Event('disableAndClear')); - } else if (isMetaMaskConnected()) { - globalContext.connected = true; - } - - updateOnboardElements(); - updateContractElements(); -}; -``` - -- [ ] **Step 6: Update onboarding controls** - -Replace `updateOnboardElements` with: - -```javascript -const updateOnboardElements = () => { - let onboarding; - try { - onboarding = new MetaMaskOnboarding({ forwarderOrigin }); - } catch (error) { - console.error(error); - } - - if (!isProviderAvailableForDapp()) { - onboardButton.innerText = 'Click here to install MetaMask!'; - onboardButton.onclick = () => { - onboardButton.innerText = 'Onboarding in progress'; - onboardButton.disabled = true; - onboarding.startOnboarding(); - }; - onboardButton.disabled = false; - return; - } - - document.dispatchEvent(new Event('MetaMaskInstalled')); - - if (isMetaMaskConnected()) { - onboardButton.innerText = 'Connected'; - onboardButton.disabled = true; - if (onboarding) { - onboarding.stopOnboarding(); - } - } else { - onboardButton.innerText = 'Connect'; - onboardButton.onclick = async () => { - try { - const newAccounts = await globalContext.provider.request({ - method: 'eth_requestAccounts', - }); - handleNewAccounts(newAccounts); - } catch (error) { - console.error(error); - } - }; - onboardButton.disabled = false; - } - - if (isWalletConnectConnected) { - if (onboarding) { - onboarding.stopOnboarding(); - } - if ('autoRefreshOnNetworkChange' in globalContext.provider) { - globalContext.provider.autoRefreshOnNetworkChange = false; - } - getNetworkAndChainId(); - - if (canSubscribeToProviderEvents()) { - globalContext.provider.on('chainChanged', handleNewChain); - globalContext.provider.on('chainChanged', handleEIP1559Support); - globalContext.provider.on('chainChanged', handleNewNetwork); - globalContext.provider.on('accountsChanged', handleNewAccounts); - globalContext.provider.on('accountsChanged', handleEIP1559Support); - } - } -}; -``` - -- [ ] **Step 7: Verify provider initialization** - -Run: - -```bash -yarn lint:eslint src/index.js -``` - -Expected: - -- Exit code `0`. -- No ESLint failures. - -- [ ] **Step 8: Commit task 5** - -Run: - -```bash -git add src/index.js -git commit -m "feat: support eip1193 provider initialization" -``` - -Expected: - -- Commit succeeds. - ---- - -### Task 6: Run Build Verification And Fix Integration Issues - -**Files:** - -- Modify only files already touched in Tasks 1-5 unless the build output identifies a specific missing browser fallback. - -- [ ] **Step 1: Run full lint** - -Run: - -```bash -yarn lint -``` - -Expected: - -- Exit code `0`. -- No ESLint or Prettier failures. - -- [ ] **Step 2: Run production build** - -Run: - -```bash -yarn build -``` - -Expected: - -- Exit code `0`. -- `dist/main.js` and other build artifacts are emitted. - -- [ ] **Step 3: Inspect changed files** - -Run: - -```bash -git status --short -git diff --stat -``` - -Expected: - -- Source changes match this plan. -- Build artifacts in `dist/` are ignored or intentionally absent from git status. - -- [ ] **Step 4: Commit verification fixes** - -If Tasks 6.1 or 6.2 required source fixes, run: - -```bash -git add package.json yarn.lock src/dapp-metadata.js src/connections.js src/connect-evm.js src/components/connections/connections.js src/index.js -git commit -m "fix: stabilize connect evm integration" -``` - -Expected: - -- Commit succeeds when there are source fixes. -- If there are no source fixes, `git status --short` shows no uncommitted source changes. - ---- - -### Task 7: Manual Smoke Verification - -**Files:** - -- No planned source edits. - -- [ ] **Step 1: Start the dev server** - -Run: - -```bash -yarn start -``` - -Expected: - -- webpack-dev-server starts on port `9011`. -- The dapp is available at `http://localhost:9011`. - -- [ ] **Step 2: Verify existing connection paths** - -In a browser, verify: - -- `Use window.ethereum` still appears and can select the injected provider when available. -- EIP-6963 provider cards still render when wallets announce providers. -- `Connect` still calls `eth_requestAccounts` on the active provider. -- `SDK Connect` still connects and toggles to `Sdk Connect - Disconnect`. -- `Wallet Connect` still opens Web3Modal. - -Expected: - -- Existing paths work as they did before this feature. -- No console error appears during initial page load. - -- [ ] **Step 3: Verify Connect EVM path** - -In a browser, verify: - -- `Connect EVM` button appears in the Connect Actions card. -- Clicking `Connect EVM` initiates a MetaMask Connect EVM connection. -- Successful connection changes the button text to `Connect EVM - Disconnect`. -- Active Provider shows: - - UUID: `connect-evm` - - Name: `connect-evm` - - Icon: local SDK icon -- Status shows non-empty Accounts. -- Status shows a ChainId. - -Expected: - -- Existing transaction/signature panels become enabled after accounts are present. -- The console does not show `net_version` as an uncaught initialization failure. - -- [ ] **Step 4: Verify core EIP-1193 requests** - -Use the page controls: - -- Click `eth_accounts`. -- Click a personal sign action. -- Use the network picker to switch to Sepolia or another configured supported chain. - -Expected: - -- `eth_accounts` returns the connected account. -- Personal sign opens a wallet request or returns a user-rejection error when rejected. -- Network switching either succeeds or shows the existing network error message without breaking the page. - -- [ ] **Step 5: Stop the dev server** - -Stop the `yarn start` process with `Ctrl-C`. - -Expected: - -- Dev server exits cleanly. - ---- - -### Task 8: Final Review - -**Files:** - -- No planned source edits. - -- [ ] **Step 1: Review final diff** - -Run: - -```bash -git log --oneline -8 -git status --short -``` - -Expected: - -- Recent commits correspond to this plan's tasks. -- `git status --short` has no uncommitted source changes. - -- [ ] **Step 2: Summarize implementation** - -Prepare a final summary containing: - -- Dependency added: `@metamask/connect-evm`. -- New button: `Connect EVM`. -- New module: `src/connect-evm.js`. -- Provider initialization change: EIP-1193 `request` capability replaces the old `isMetaMask` gate. -- Verification run: `yarn lint`, `yarn build`, and manual smoke checks. - -Expected: - -- The summary is short and includes any verification that was not run. diff --git a/docs/superpowers/specs/2026-05-28-connect-evm-design.md b/docs/superpowers/specs/2026-05-28-connect-evm-design.md deleted file mode 100644 index 8c72221d..00000000 --- a/docs/superpowers/specs/2026-05-28-connect-evm-design.md +++ /dev/null @@ -1,216 +0,0 @@ -# MetaMask Connect EVM Test Dapp Design - -## Goal - -Add an optional MetaMask Connect EVM connection path to the test dapp without replacing or regressing the existing `window.ethereum`, EIP-6963, MetaMask SDK, or WalletConnect flows. - -The user-facing result is an additional `Connect EVM` button in the existing Connect Actions card. Clicking it connects through the published `@metamask/connect-evm` package and makes its EIP-1193 provider the active provider used by the rest of the page. - -## Non-Goals - -- Do not remove `@metamask/sdk` or change the behavior of the existing `SDK Connect` button. -- Do not replace WalletConnect/Web3Modal. -- Do not make `connect-evm` appear as an EIP-6963 provider unless the package itself provides that support. -- Do not convert the app to React, TypeScript, wagmi, viem, or a new app architecture. -- Do not require an Infura API key for local development. - -## Current State - -The dapp already centralizes most wallet interactions through `globalContext.provider`. That makes an additional provider source feasible, because existing transaction, signature, permissions, and network components call the active provider through the same EIP-1193 request surface. - -The current connection paths are: - -- `window.ethereum`, selected on startup when available. -- EIP-6963 provider discovery and manual selection. -- `@metamask/sdk` via `SDK Connect`. -- WalletConnect/Web3Modal via `Wallet Connect`. - -The main compatibility issues for `@metamask/connect-evm` are: - -- The app treats `globalContext.provider.isMetaMask` as the gate for installing listeners and enabling many controls. The `connect-evm` provider is EIP-1193 compatible but does not expose `isMetaMask`. -- Provider initialization calls `net_version`. `connect-evm` marks `net_version` unsupported, so initialization must derive the decimal network id from `eth_chainId` when `net_version` is unavailable. -- `connect-evm` needs an explicit `api.supportedNetworks` map of hex chain IDs to RPC URLs. - -## Approach - -Use an additive connection path: - -1. Add the published `@metamask/connect-evm` package. -2. Add a focused `src/connect-evm.js` module that owns `createEVMClient`, supported network configuration, connect, disconnect, and provider-detail creation. -3. Add a `Connect EVM` button to the existing Connect Actions card. -4. Wire the new button in `src/index.js` so a successful connection calls `setActiveProviderDetail()` with the `connect-evm` provider. -5. Relax provider capability checks from "is MetaMask installed" to "is this active provider usable by this dapp." -6. Keep legacy provider behavior unchanged where possible. - -## Supported Networks - -The first implementation supports a stable core set aligned with the dapp's existing sample-contract and network-switching coverage: - -- Ethereum Mainnet: `0x1` -- Sepolia: `0xaa36a7` -- OP Mainnet: `0xa` -- Polygon Mainnet: `0x89` -- Base Mainnet: `0x2105` -- Arbitrum One: `0xa4b1` -- Avalanche C-Chain: `0xa86a` -- Binance Smart Chain: `0x38` -- Localhost 8545: `0x539` -- Localhost 8546: `0x53a` - -Use public RPC URLs for public networks only where they are acceptable for a test dapp. Local chains use `http://127.0.0.1:8545` and `http://127.0.0.1:8546`. - -The network picker continues to display more chains than `connect-evm` supports. If a user selects an unsupported chain while `connect-evm` is active, the app shows the existing network error path instead of breaking the active provider state. - -## Components - -### `src/connect-evm.js` - -Responsibilities: - -- Import `createEVMClient` from `@metamask/connect-evm`. -- Keep a singleton client promise so repeated clicks do not create competing clients. -- Define `CONNECT_EVM_SUPPORTED_NETWORKS`. -- Define `CONNECT_EVM_CHAIN_IDS` from the supported network keys. -- Initialize the client with existing dapp metadata: - - `name: 'E2e Test Dapp'` - - `description: 'This is the E2e Test Dapp'` - - `url: 'https://metamask.github.io/test-dapp/'` -- Pass `api.supportedNetworks`. -- Expose `handleConnectEvm(name, button, isConnected)`. -- On connect: - - call `client.connect({ chainIds: CONNECT_EVM_CHAIN_IDS })` - - get `client.getProvider()` - - create a provider detail shaped like existing EIP-6963 details - - set it active - - update accounts and button state -- On disconnect: - - call `client.disconnect()` - - clear accounts and active UI state - - remove the provider detail - - restore button text and style - -### `src/components/connections/connections.js` - -Add a new button: - -- `id="connectEvm"` -- text: `Connect EVM` -- same existing button classes as `SDK Connect` and `Wallet Connect` - -Do not rearrange or rename existing controls. - -### `src/index.js` - -Responsibilities: - -- Import the `connect-evm` handler. -- Track `isConnectEvmConnected` alongside SDK and WalletConnect state. -- Wire `connectEvmBtn.onclick`. -- Export `updateConnectEvmConnectionState`. -- Treat the `connect-evm` provider as an active provider that gets: - - `chainChanged` - - `accountsChanged` - - `disconnect`, if supported by the provider -- Avoid duplicating listeners when providers change. - -Provider capability checks are renamed away from install-specific language: - -- `isMetaMaskInstalled()` becomes `isProviderAvailableForDapp()`. -- The check returns true when `globalContext.provider` has a `request` function and either: - - `provider.isMetaMask` is true, or - - the selected provider detail is the `connect-evm` provider, or - - the provider was selected from EIP-6963 or WalletConnect. - -Existing call sites are updated to use `isProviderAvailableForDapp()` without changing the behavior of legacy providers. - -## Data Flow - -Connection flow: - -1. User clicks `Connect EVM`. -2. The handler initializes or reuses the `createEVMClient()` singleton. -3. The handler calls `client.connect({ chainIds })`. -4. The handler retrieves `client.getProvider()`. -5. The handler builds a provider detail: - - `info.uuid`: a stable local identifier, such as `connect-evm` - - `info.name`: `connect-evm` - - `info.icon`: `./sdk-connect.svg` unless a better local asset is added - - `info.rdns`: `io.metamask` - - `provider`: the EIP-1193 provider -6. `setActiveProviderDetail()` installs the provider as `globalContext.provider`. -7. Existing status, account, network, contract, transaction, and signature components continue through `globalContext.provider`. - -Disconnect flow: - -1. User clicks `Connect EVM - Disconnect`. -2. The handler calls `client.disconnect()`. -3. The handler clears accounts, removes the provider detail, updates button state, and emits the same local UI state changes as the other connection paths. -4. If the active provider was `connect-evm`, reset active provider status in the UI without changing other provider entries. - -## Network and Chain Handling - -`getNetworkAndChainId()` continues to call `eth_chainId` first. - -For `net_version`: - -- Existing providers keep their current behavior. -- If `net_version` fails with an unsupported-method error or any error from `connect-evm`, derive the decimal network id from `eth_chainId`. -- Continue calling `handleNewNetwork(networkId)` with a string network id. - -`wallet_switchEthereumChain` is supported by `connect-evm` for configured chains. The network picker uses existing error UI for unsupported chains or wallet rejections. - -## Error Handling - -Button behavior: - -- Disable the `Connect EVM` button while a connection or disconnect is in progress. -- Restore the previous label and style on failure. -- Log connection errors to the console, following the existing dapp pattern. -- Do not clear an already active non-Connect-EVM provider if Connect EVM connection fails. - -Known user-facing errors: - -- `4001`: user rejected the request. -- `-32002`: connection request already pending. -- unsupported chain: show the existing network error path. -- unsupported RPC method: show the requesting component's existing error display. - -Unsupported methods are acceptable for this test dapp as long as they fail locally in the specific test panel and do not break provider initialization or global UI state. - -## Testing - -Automated checks: - -- `yarn lint` -- `yarn build` - -Manual smoke checks: - -- Existing `window.ethereum` startup path still works. -- EIP-6963 providers still render and can become active. -- Existing `Connect` button still calls `eth_requestAccounts`. -- Existing `SDK Connect` button still connects and disconnects. -- Existing `Wallet Connect` button still opens and connects as before. -- New `Connect EVM` button connects, updates accounts, active chain, and active provider display. -- `Connect EVM - Disconnect` clears the Connect EVM session and button state. -- `eth_accounts`, `eth_chainId`, `personal_sign`, and a simple transaction request work when supported by the connected wallet. -- Network picker switches to a configured supported chain. -- Network picker failure for unsupported chains does not break the app. -- A component using `net_version` fallback initializes correctly under Connect EVM. - -## Documentation References - -- MetaMask Connect EVM introduction: https://docs.metamask.io/metamask-connect/evm/ -- MetaMask Connect EVM JavaScript quickstart: https://docs.metamask.io/metamask-connect/evm/quickstart/javascript/ -- Local package source audited for compatibility: `/Users/aphex/repos/metamask/connect-monorepo/packages/connect-evm` - -## Acceptance Criteria - -- The repo depends on the published `@metamask/connect-evm` package. -- The Connect Actions card includes a `Connect EVM` button. -- Clicking `Connect EVM` connects through `@metamask/connect-evm`. -- The provider returned by `client.getProvider()` becomes the app's active provider. -- Existing connection paths continue to work. -- Provider initialization no longer depends on `net_version` succeeding. -- Existing request/signature/transaction panels remain disabled until an active provider and account are available. -- Lint and production build pass. From 06c2818dcc7ad04fe60f284e7db691fca0d87dcd Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 28 May 2026 15:23:09 +0100 Subject: [PATCH 21/21] fix: rename connect evm button label --- src/components/connections/connections.js | 2 +- src/connect-evm.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/connections/connections.js b/src/components/connections/connections.js index 54e93d0a..657aeda7 100644 --- a/src/components/connections/connections.js +++ b/src/components/connections/connections.js @@ -34,7 +34,7 @@ export function connectionsComponent(parentContainer) { + >MetaMask Connect