Repository: trezor/trezor-suite
Pull request #18702: feat(suite): move connect ws to experimental features
Author: login: martykan
Created: 2025-05-02T14:51:06Z | Merged: 2025-05-06T08:26:17Z

## Description

Add flags to experimental features for Connect Websocket server and WalletConnect

<img width="924" alt="Screenshot 2025-05-05 at 13 57 07" src="https://github.com/user-attachments/assets/7e4dd3ab-0ea4-491d-8418-9bf70bbb0106" />

🔍🖥️ **Suite web test results:** [View in Currents](https://app.currents.dev/projects/Og0NOQ/runs?branch=feat/connect-ws-experimental-feature&lookback=7)

🔍🖥️ **Suite desktop test results:** [View in Currents](https://app.currents.dev/projects/4ytF0E/runs?branch=feat/connect-ws-experimental-feature&lookback=7)

🔍🖥️ **Suite native android test results:** [View in Currents](https://app.currents.dev/projects/iUe1Y4/runs?branch=feat/connect-ws-experimental-feature&lookback=7)


Diff:
diff --git a/packages/suite/src/actions/suite/initAction.ts b/packages/suite/src/actions/suite/initAction.ts
index ea5ca4a2f3a8..bbd97b9ad78f 100644
--- a/packages/suite/src/actions/suite/initAction.ts
+++ b/packages/suite/src/actions/suite/initAction.ts
@@ -18,7 +18,7 @@ import * as languageActions from 'src/actions/settings/languageActions';
 import * as analyticsActions from 'src/actions/suite/analyticsActions';
 import * as metadataLabelingActions from 'src/actions/suite/metadataLabelingActions';
 import * as routerActions from 'src/actions/suite/routerActions';
-import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
+import { selectHasExperimentalFeature } from 'src/reducers/suite/suiteReducer';
 import type { Dispatch, GetState } from 'src/types/suite';
 
 import { SUITE } from './constants';
@@ -142,7 +142,7 @@ export const init = () => async (dispatch: Dispatch, getState: GetState) => {
     dispatch(periodicCheckStakeDataThunk());
 
     // 14. init wallet connect
-    if (selectIsDebugModeActive(getState())) {
+    if (selectHasExperimentalFeature('walletconnect')(getState())) {
         dispatch(walletConnectActions.walletConnectInitThunk());
     }
 
diff --git a/packages/suite/src/components/settings/SettingsLayout.tsx b/packages/suite/src/components/settings/SettingsLayout.tsx
index 55dbab575b4b..ad27f0907d16 100644
--- a/packages/suite/src/components/settings/SettingsLayout.tsx
+++ b/packages/suite/src/components/settings/SettingsLayout.tsx
@@ -12,7 +12,10 @@ import {
     SubpageNavigation,
 } from 'src/components/suite/layouts/SuiteLayout';
 import { useDiscovery, useDispatch, useLayout, useSelector } from 'src/hooks/suite';
-import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
+import {
+    selectHasExperimentalFeature,
+    selectIsDebugModeActive,
+} from 'src/reducers/suite/suiteReducer';
 import { SettingsLoading } from 'src/views/settings/SettingsLoader';
 
 type SettingsLayoutProps = {
@@ -23,6 +26,8 @@ type SettingsLayoutProps = {
 
 const SettingsHeader = () => {
     const isDebugModeActive = useSelector(selectIsDebugModeActive);
+    const enabledTrezorConnectWS = useSelector(selectHasExperimentalFeature('trezor-connect-ws'));
+    const enabledWalletConnect = useSelector(selectHasExperimentalFeature('walletconnect'));
 
     const dispatch = useDispatch();
 
@@ -53,7 +58,7 @@ const SettingsHeader = () => {
                 id: 'settings-connected-apps',
                 title: <Translation id="TR_CONNECTED_APPS" />,
                 position: 'primary',
-                isHidden: !isDebugModeActive || !isDesktop(),
+                isHidden: !isDesktop() || !(enabledTrezorConnectWS || enabledWalletConnect),
                 'data-testid': '@settings/menu/connected-apps',
                 callback: () => dispatch(goto('settings-connected-apps', { preserveParams: true })),
             },
@@ -66,7 +71,7 @@ const SettingsHeader = () => {
                 callback: () => dispatch(goto('settings-debug', { preserveParams: true })),
             },
         ],
-        [dispatch, isDebugModeActive],
+        [dispatch, isDebugModeActive, enabledTrezorConnectWS, enabledWalletConnect],
     );
 
     return (
diff --git a/packages/suite/src/constants/suite/experimental.ts b/packages/suite/src/constants/suite/experimental.ts
index a1e5714107ba..f44cd41962b9 100644
--- a/packages/suite/src/constants/suite/experimental.ts
+++ b/packages/suite/src/constants/suite/experimental.ts
@@ -6,7 +6,12 @@ import { EXPERIMENTAL_PASSWORD_MANAGER_KB_URL, HELP_CENTER_TOR_URL, Url } from '
 
 import { Dispatch } from '../../types/suite';
 
-export type ExperimentalFeature = 'password-manager' | 'tor-external' | 'nft-section';
+export type ExperimentalFeature =
+    | 'password-manager'
+    | 'tor-external'
+    | 'nft-section'
+    | 'trezor-connect-ws'
+    | 'walletconnect';
 
 export type ExperimentalFeatureConfig = {
     title: TranslationKey;
@@ -43,4 +48,17 @@ export const EXPERIMENTAL_FEATURES: Record<ExperimentalFeature, ExperimentalFeat
         title: 'TR_EXPERIMENTAL_NFT_SECTION',
         description: 'TR_EXPERIMENTAL_NFT_SECTION_DESCRIPTION',
     },
+    'trezor-connect-ws': {
+        title: 'TR_EXPERIMENTAL_TREZORCONNECT_WS',
+        description: 'TR_EXPERIMENTAL_TREZORCONNECT_WS_DESCRIPTION',
+        isDisabled: () => !isDesktop(),
+        onToggle: async ({ newValue }) => {
+            await desktopApi.connectPopupSetEnabled(newValue);
+        },
+    },
+    walletconnect: {
+        title: 'TR_WALLETCONNECT',
+        description: 'TR_EXPERIMENTAL_WALLETCONNECT_DESCRIPTION',
+        isDisabled: () => !isDesktop(),
+    },
 };
diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts
index c345e47cce93..3e61a7bba16c 100644
--- a/packages/suite/src/support/messages.ts
+++ b/packages/suite/src/support/messages.ts
@@ -9858,4 +9858,17 @@ export default defineMessages({
         id: 'TR_QUIT_NOW',
         defaultMessage: 'Quit now',
     },
+    TR_EXPERIMENTAL_TREZORCONNECT_WS: {
+        id: 'TR_EXPERIMENTAL_TREZORCONNECT_WS',
+        defaultMessage: 'Trezor Connect and WalletConnect integration in Suite',
+    },
+    TR_EXPERIMENTAL_TREZORCONNECT_WS_DESCRIPTION: {
+        id: 'TR_EXPERIMENTAL_TREZORCONNECT_WS_DESCRIPTION',
+        defaultMessage:
+            'Interact with 3rd party apps through Suite for a seamless user experience. Enabling will restart the app.',
+    },
+    TR_EXPERIMENTAL_WALLETCONNECT_DESCRIPTION: {
+        id: 'TR_EXPERIMENTAL_WALLETCONNECT_DESCRIPTION',
+        defaultMessage: 'Use WalletConnect to connect your Trezor to Ethereum dApps.',
+    },
 } as const);
diff --git a/packages/suite/src/support/suite/useConnectPopupDesktop.tsx b/packages/suite/src/support/suite/useConnectPopupDesktop.tsx
index e7845664f0e1..6b64845bca3b 100644
--- a/packages/suite/src/support/suite/useConnectPopupDesktop.tsx
+++ b/packages/suite/src/support/suite/useConnectPopupDesktop.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 
 import {
     connectPopupCallThunk,
@@ -9,14 +9,21 @@ import { CALL_SOURCE_DESKTOP_WS } from '@suite-common/connect-popup/src/connectP
 import TrezorConnect from '@trezor/connect';
 import { desktopApi } from '@trezor/suite-desktop-api';
 
+import { SUITE } from 'src/actions/suite/constants';
 import { openModal } from 'src/actions/suite/modalActions';
 import { useDispatch, useSelector } from 'src/hooks/suite';
 
 export const useConnectPopupDesktop = () => {
     const dispatch = useDispatch();
     const popupCall = useSelector(selectConnectPopupCall);
+    const experimentalFeatures = useSelector(state => state.suite.settings.experimental);
+    const initialized = useRef(false);
 
     useEffect(() => {
+        // Prevent multiple initializations
+        if (initialized.current) return;
+        initialized.current = true;
+
         const init = async () => {
             if (desktopApi.available && (await desktopApi.connectPopupEnabled())) {
                 desktopApi.on('connect-popup/call', async params => {
@@ -45,6 +52,17 @@ export const useConnectPopupDesktop = () => {
                     dispatch(openModal({ type: 'auto-start-before-quit' }));
                 });
                 desktopApi.connectPopupReady();
+
+                // Update experimental features value
+                // TODO: remove this when out of experimental
+                if (!experimentalFeatures?.includes('trezor-connect-ws')) {
+                    dispatch({
+                        type: SUITE.SET_EXPERIMENTAL_FEATURES,
+                        payload: {
+                            enabledFeatures: [...(experimentalFeatures ?? []), 'trezor-connect-ws'],
+                        },
+                    });
+                }
             }
         };
         init();
@@ -55,7 +73,7 @@ export const useConnectPopupDesktop = () => {
                 desktopApi.removeAllListeners('connect-popup/cancel');
             }
         };
-    }, [dispatch]);
+    }, [dispatch, experimentalFeatures]);
 
     // App focus control
     const [currentlyOngoing, setCurrentlyOngoing] = useState(false);
diff --git a/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx b/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx
index d5002266bd4c..58ade65806ea 100644
--- a/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx
+++ b/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx
@@ -8,6 +8,7 @@ import { spacings } from '@trezor/theme';
 
 import { Translation } from 'src/components/suite';
 import { useSelector } from 'src/hooks/suite';
+import { selectHasExperimentalFeature } from 'src/reducers/suite/suiteReducer';
 
 import { ConnectPermissions } from './ConnectPermissions';
 import { WalletConnectButton } from './WalletConnectButton';
@@ -17,7 +18,9 @@ export const SettingsConnectedApps = () => {
     const hasSupportedDevice =
         useSelector(selectDevices).find(device => !hasBitcoinOnlyFirmware(device)) !== undefined;
     const hasExistingWalletConnectSessions = useSelector(selectSessions).length > 0;
-    const wcEnabled = hasSupportedDevice || hasExistingWalletConnectSessions;
+    const wcFeatureFlag = useSelector(selectHasExperimentalFeature('walletconnect'));
+    const connectFeatureFlag = useSelector(selectHasExperimentalFeature('trezor-connect-ws'));
+    const wcEnabled = wcFeatureFlag && (hasSupportedDevice || hasExistingWalletConnectSessions);
 
     const tabs = [
         {
@@ -25,7 +28,7 @@ export const SettingsConnectedApps = () => {
             icon: 'trezorLogo' as const,
             title: <Translation id="TR_TREZOR_CONNECT" />,
             component: <ConnectPermissions />,
-            isEnabled: true,
+            isEnabled: connectFeatureFlag,
         },
         {
             id: 'walletconnect',
diff --git a/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx b/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
index 994fab61a8f1..acfd3f47b1c9 100644
--- a/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
+++ b/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
@@ -23,7 +23,6 @@ import { TranslationMode } from './TranslationMode';
 import { Transport } from './Transport';
 import { TransportBackends } from './TransportBackends';
 import { TrezorConnectLogs } from './TrezorConnectLogs';
-import { TrezorConnectWS } from './TrezorConnectWS';
 import { TriggerHighlight } from './TriggerHighlight';
 import { ViewOnlySettings } from './ViewOnlySettings';
 import { WipeData } from './WipeData';
@@ -95,7 +94,6 @@ export const SettingsDebug = () => {
             )}
             <SettingsSection title="TrezorConnect">
                 <TrezorConnectLogs />
-                {isDesktop() && <TrezorConnectWS />}
             </SettingsSection>
         </SettingsLayout>
     );
diff --git a/packages/suite/src/views/settings/SettingsDebug/TrezorConnectWS.tsx b/packages/suite/src/views/settings/SettingsDebug/TrezorConnectWS.tsx
deleted file mode 100644
index daff58dc33e8..000000000000
--- a/packages/suite/src/views/settings/SettingsDebug/TrezorConnectWS.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useEffect, useState } from 'react';
-
-import { Switch } from '@trezor/components';
-import { desktopApi } from '@trezor/suite-desktop-api';
-
-import { ActionColumn, SectionItem, TextColumn } from 'src/components/suite';
-
-export const TrezorConnectWS = () => {
-    const [websocketEnabled, setWebsocketEnabled] = useState(false);
-    const updateWebsocketStatus = () => {
-        desktopApi.connectPopupEnabled().then(result => {
-            setWebsocketEnabled(result);
-        });
-    };
-    useEffect(() => {
-        updateWebsocketStatus();
-    }, []);
-    const toggleWebsocket = () => {
-        desktopApi.connectPopupSetEnabled(!websocketEnabled).then(() => {
-            updateWebsocketStatus();
-        });
-    };
-
-    return (
-        <SectionItem>
-            <TextColumn
-                title="TrezorConnect WebSocket server"
-                description="Enables new Connect-in-Suite workflow"
-            />
-            <ActionColumn>
-                <Switch isChecked={websocketEnabled} onChange={toggleWebsocket} />
-            </ActionColumn>
-        </SectionItem>
-    );
-};
diff --git a/suite-common/walletconnect/src/walletConnectMiddleware.ts b/suite-common/walletconnect/src/walletConnectMiddleware.ts
index 8a929ccdb703..a601a0005880 100644
--- a/suite-common/walletconnect/src/walletConnectMiddleware.ts
+++ b/suite-common/walletconnect/src/walletConnectMiddleware.ts
@@ -35,8 +35,11 @@ export const prepareWalletConnectMiddleware = createMiddlewareWithExtraDeps(
             dispatch(extra.actions.onModalCancel());
         }
 
-        // TODO: remove after feature is out of Suite debug
-        if (action.type === '@suite/set-debug-mode' && action.payload.showDebugMenu) {
+        // TODO: remove after feature is out of experimental
+        if (
+            action.type === '@suite/set-experimental-features' &&
+            action.payload.enabledFeatures.includes('walletconnect')
+        ) {
             dispatch(walletConnectThunks.walletConnectInitThunk());
         }
 


Reviews:
user: login: mroz22
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:13:39Z
user: login: martykan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T07:20:08Z
user: login: mroz22
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:54:26Z

Review comments:
diff_hunk: @@ -55,7 +68,7 @@ export const useConnectPopupDesktop = () => {
                 desktopApi.removeAllListeners('connect-popup/cancel');
             }
         };
-    }, [dispatch]);
+    }, [dispatch, experimentalFeatures]);
path: packages/suite/src/support/suite/useConnectPopupDesktop.tsx
user: login: mroz22
body: init is now called everytime `experimentalFeatures` changes? doesn't it also mean  that you would register listners multiple times when toggling? 
created_at: 2025-05-06T07:13:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,7 +68,7 @@ export const useConnectPopupDesktop = () => {
                 desktopApi.removeAllListeners('connect-popup/cancel');
             }
         };
-    }, [dispatch]);
+    }, [dispatch, experimentalFeatures]);
path: packages/suite/src/support/suite/useConnectPopupDesktop.tsx
user: login: martykan
body: True, adding a check https://github.com/trezor/trezor-suite/pull/18702/commits/e25e526d68a418da258b1e61799b70fdea7df9f3
created_at: 2025-05-06T07:20:08Z
author_association: MEMBER

PR comments:
user: login: trezor-bot[bot]
created_at: 2025-05-05T11:44:52Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-05T11:59:18Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-05T12:12:28Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-05T12:14:29Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-05T15:11:07Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-06T07:20:23Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-06T07:56:35Z
body: ✅ Previously successful run of **[Test] PR Suite Desktop e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: trezor-bot[bot]
created_at: 2025-05-06T07:56:36Z
body: ✅ Previously successful run of **[Test] PR Suite Web e2e tests** workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)
author_association: CONTRIBUTOR
user: login: sentry[bot]
created_at: 2025-05-06T09:50:32Z
body: ## Suspect Issues
This pull request was deployed and Sentry observed the following issues:

- ‼️ **TypeError: Cannot read properties of undefined (reading 'includes')** `prepareWalletConnectMiddleware(@trezor/suite-bu...` [View Issue](https://satoshilabs.sentry.io/issues/6588495232/?referrer=github-pr-bot)

<sub>Did you find this useful? React with a 👍 or 👎</sub>
author_association: NONE
user: login: evgenysl
created_at: 2025-05-06T10:11:21Z
body: QA OK

<img width="1514" alt="Screenshot 2025-05-06 at 12 08 59" src="https://github.com/user-attachments/assets/d3d1f53b-faa7-440c-ba06-dade95c7b815" />



**Info:**
 - Suite version: desktop 25.5.0 (63d2cacd8acdc6053f8f6920fcd6cc3868821d29)
 - Browser: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) TrezorSuiteDev/25.5.0 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36
 - OS: MacIntel
 - Screen: 1512x982
 - Device: Trezor T3T1 2.8.5 regular (revision a61e783448043760a7c925e017bd08a4673a61c8)
 - Transport: BridgeTransport 2.0.33
author_association: CONTRIBUTOR