diff --git a/apps/fxblox-web/src/components/setup/WalletSigner.tsx b/apps/fxblox-web/src/components/setup/WalletSigner.tsx index 144c0c4..7e95c0e 100644 --- a/apps/fxblox-web/src/components/setup/WalletSigner.tsx +++ b/apps/fxblox-web/src/components/setup/WalletSigner.tsx @@ -42,7 +42,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FxBox, FxButton, FxSpinner, FxText } from '@functionland/fx-ui'; -import { useLogger } from '@/hooks/useLogger'; import { useColorMode } from '@/stores/useSettingsStore'; import { getAppKit, setAppKitTheme } from '@/wallet/appkit'; import { diag } from '@/wallet/diag'; @@ -151,12 +150,9 @@ export default function WalletSigner({ const [requestLink, setRequestLink] = useState(null); const requestLinkRef = useRef(null); requestLinkRef.current = requestLink; - // Debug mode turns the automatic app-switch off — see the hop below for why that is a diagnostic, not a feature. - const { isDebugModeEnable: debug } = useLogger(); - const [hopSkippedForDebug, setHopSkippedForDebug] = useState(false); // Latest props / wallet state for the stable callbacks below (the mobile effects closed over stale state). - const latest = useRef({ password, onLinkingChange, onPhaseChange, onSignature, onError, wallet, debug }); - latest.current = { password, onLinkingChange, onPhaseChange, onSignature, onError, wallet, debug }; + const latest = useRef({ password, onLinkingChange, onPhaseChange, onSignature, onError, wallet }); + latest.current = { password, onLinkingChange, onPhaseChange, onSignature, onError, wallet }; useEffect(() => { setAppKitTheme(mode); @@ -203,7 +199,6 @@ export default function WalletSigner({ setShowNudge(false); setShowStuckHint(false); setWalletShowedNothing(false); - setHopSkippedForDebug(false); wentToWalletRef.current = false; return undefined; } @@ -327,17 +322,6 @@ export default function WalletSigner({ // remaining case is a navigation in a shape we did not recognise: the wallet is already in front, and // hopping again would bounce the user twice. if (capture.captured() || !capture.sawOpen()) { - // Debug mode: leave the wallet alone. Every retry so far went into a MetaMask that the FIRST hop had - // already wedged on its splash screen, so none of them tested whether the deep link itself is what - // wedges a healthy warm wallet — while the one recovery the logs show working was a cold MetaMask - // picking the request up with no deep link at all. With the request on the relay and no hop, the - // user can switch to the wallet by hand and see what it shows; the button is still there for the - // deep link afterwards. Debug mode is an explicit opt-in used for exactly this kind of report. - if (latest.current.debug) { - diag('[sign] debug mode: NOT opening the wallet — switch to it by hand, or tap the button'); - setHopSkippedForDebug(true); - return; - } diag(`[sign] opening the wallet on the request: ${link} activation=${userActivation()}`); wentToWalletRef.current = true; hopToWallet(link); @@ -418,11 +402,6 @@ export default function WalletSigner({ {t('setup.linkPassword.walletStuckHint')} )} - {hopSkippedForDebug && ( - - {t('setup.linkPassword.debugNoAutoOpen')} - - )} {showOpenWallet && walletLink && ( {t('setup.linkPassword.openWalletToApprove')} diff --git a/apps/fxblox-web/src/i18n/locales/en/setup.json b/apps/fxblox-web/src/i18n/locales/en/setup.json index 1ee8522..d16a629 100644 --- a/apps/fxblox-web/src/i18n/locales/en/setup.json +++ b/apps/fxblox-web/src/i18n/locales/en/setup.json @@ -58,9 +58,8 @@ "signaturePortalHint": "Sign at fxblox.fx.land, then paste the signature below together with the wallet address you signed with.", "walletConnectedTapSign": "Wallet connected. Tap below to approve the signature — this opens your wallet again.", "approveInWallet": "Approve the request in your wallet…", - "walletStuckHint": "Wallet opened but stuck on its splash screen? Close it completely from your recent apps, then tap Open wallet to approve again — tapping without closing it first will not help. Your request stays valid. If this happens every time, Android is restricting the wallet in the background: set MetaMask and your browser to Unrestricted under Settings → Apps → (app) → Battery, and it stops.", - "openWalletToApprove": "Open wallet to approve", - "debugNoAutoOpen": "Debug mode: the wallet was not opened automatically. The request is waiting on the relay — switch to your wallet from your recent apps and note what it shows, or tap Open wallet to approve." + "walletStuckHint": "Wallet opened but stuck on its splash screen? Close it completely from your recent apps, then open it again — from its icon, or with Open wallet to approve — and it will show the request. Opening it again without closing it first will not help. Your request stays valid, so nothing is lost.", + "openWalletToApprove": "Open wallet to approve" }, "connectToBlox": { "intro": "Turn your Blox on, then connect to it with Bluetooth. We'll suggest other ways if that doesn't work.", diff --git a/apps/fxblox-web/src/i18n/locales/zh/setup.json b/apps/fxblox-web/src/i18n/locales/zh/setup.json index d519dcb..f529ef0 100644 --- a/apps/fxblox-web/src/i18n/locales/zh/setup.json +++ b/apps/fxblox-web/src/i18n/locales/zh/setup.json @@ -58,9 +58,8 @@ "signaturePortalHint": "请在 fxblox.fx.land 完成签名,然后将签名与用于签名的钱包地址一起粘贴到下方。", "walletConnectedTapSign": "钱包已连接。点击下方按钮进行签名 — 这会再次打开您的钱包。", "approveInWallet": "请在您的钱包中确认此请求…", - "walletStuckHint": "钱包已打开但停在启动画面?请从最近任务中彻底关闭钱包,然后再次点击“打开钱包进行签名” — 不先关闭钱包而直接再次点击是没有用的。您的签名请求仍然有效。如果每次都这样,说明 Android 正在后台限制钱包:请在 设置 → 应用 → (应用)→ 电池 中,将 MetaMask 和您的浏览器设为“不受限制”,问题即可消除。", - "openWalletToApprove": "打开钱包进行签名", - "debugNoAutoOpen": "调试模式:未自动打开钱包。请求已在中继上等待 — 请从最近任务中切换到您的钱包并记录它显示的内容,或点击“打开钱包进行签名”。" + "walletStuckHint": "钱包已打开但停在启动画面?请从最近任务中彻底关闭钱包,然后重新打开 — 点击钱包图标或点击“打开钱包进行签名”均可 — 它会立即显示签名请求。不先关闭钱包而直接再次打开是没有用的。您的签名请求仍然有效,不会丢失。", + "openWalletToApprove": "打开钱包进行签名" }, "connectToBlox": { "intro": "打开您的 Blox,然后通过蓝牙连接。如果不成功,我们会为您提供其他方式。", diff --git a/apps/fxblox-web/src/screens/InitialSetup/__tests__/LinkPassword.test.tsx b/apps/fxblox-web/src/screens/InitialSetup/__tests__/LinkPassword.test.tsx index 5764ed3..019386f 100644 --- a/apps/fxblox-web/src/screens/InitialSetup/__tests__/LinkPassword.test.tsx +++ b/apps/fxblox-web/src/screens/InitialSetup/__tests__/LinkPassword.test.tsx @@ -52,7 +52,6 @@ vi.mock('@/platform/linking', async (importOriginal) => { }); import { WALLET_NUDGE_MS, WALLET_STUCK_MS } from '@/components/setup/WalletSigner'; -import { useSettingsStore } from '@/stores/useSettingsStore'; import { useUserProfileStore } from '@/stores/useUserProfileStore'; import * as linking from '@/platform/linking'; import * as secureStore from '@/platform/secureStore'; @@ -456,57 +455,6 @@ describe('LinkPassword', () => { } }); - it('wallet path: in debug mode the request goes out but the wallet is NOT opened automatically', async () => { - // A diagnostic, not a feature. Every retry so far went into a wallet the first hop had already wedged, so - // nothing has tested whether the deep link itself wedges a healthy warm wallet. With the request on the - // relay and no hop, the user can switch to the wallet by hand and report what it shows; the button stays - // for the deep link afterwards. - const user = userEvent.setup(); - vi.mocked(linking.assign).mockClear(); - const listeners = new Set<(payload: unknown) => void>(); - let approve: (sig: string) => void = () => undefined; - wallet.state.account = '0xABC'; - wallet.state.connected = true; - wallet.state.provider = { - request: vi.fn(() => { - for (const fn of [...listeners]) - fn({ topic: 'topic-1', request: {}, chainId: 'eip155:1', id: 42 }); - return new Promise((resolve) => (approve = resolve)); - }), - session: { peer: { metadata: { redirect: { native: 'metamask://' } } } }, - client: { - on: (_event: string, fn: (payload: unknown) => void) => listeners.add(fn), - off: (_event: string, fn: (payload: unknown) => void) => listeners.delete(fn), - }, - } as never; - useSettingsStore.setState({ - debugMode: { uniqueId: 'dbg', endDate: new Date(Date.now() + 24 * 60 * 60 * 1000) }, - }); - try { - await renderSetupAt('/setup/link-password'); - await fillPasswordAndConsent(user); - await user.click(await screen.findByTestId('sign-with-wallet')); - - // Past the macrotask the hop is deferred by, so "not yet" cannot masquerade as "never". - expect(await screen.findByTestId('debug-no-auto-open')).toBeInTheDocument(); - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 0)); - }); - expect(linking.assign).not.toHaveBeenCalled(); - - // The deep link is still one tap away. - await user.click(screen.getByTestId('open-wallet')); - expect(linking.assign).toHaveBeenCalledWith('metamask://wc?requestId=42&sessionTopic=topic-1'); - - await act(async () => { - approve('0xlate'); - }); - await waitFor(() => expect(useUserProfileStore.getState().signiture).toBe('0xlate')); - } finally { - useSettingsStore.setState({ debugMode: undefined }); - } - }); - it('wallet path: a visibility change before any hop is not treated as a wedged wallet', async () => { // Tab switches happen for all sorts of reasons. Only a return from a wallet WE sent them to is evidence. const user = userEvent.setup(); diff --git a/apps/fxblox-web/src/wallet/__tests__/appkit.test.ts b/apps/fxblox-web/src/wallet/__tests__/appkit.test.ts deleted file mode 100644 index ac311d0..0000000 --- a/apps/fxblox-web/src/wallet/__tests__/appkit.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -const createAppKitMock = vi.hoisted(() => - vi.fn((_options: unknown) => ({ setThemeMode: vi.fn(), disconnect: vi.fn() })), -); -vi.mock('@reown/appkit/react', () => ({ createAppKit: createAppKitMock })); -vi.mock('@reown/appkit-adapter-ethers5', () => ({ Ethers5Adapter: class {} })); - -import { useSettingsStore } from '@/stores/useSettingsStore'; - -/** `initAppKit` is a module-level singleton; each test wants a fresh one. */ -async function freshInitAppKit() { - vi.resetModules(); - const mod = await import('../appkit'); - return mod.initAppKit; -} - -function metadataPassed(): Record { - const call = createAppKitMock.mock.calls.at(-1)?.[0] as { metadata: Record } | undefined; - if (!call) throw new Error('createAppKit was not called'); - return call.metadata; -} - -describe('initAppKit metadata', () => { - beforeEach(() => { - createAppKitMock.mockClear(); - useSettingsStore.setState({ debugMode: undefined }); - }); - afterEach(() => { - useSettingsStore.setState({ debugMode: undefined }); - }); - - it('tells the wallet where to send the user back to, by default', async () => { - const initAppKit = await freshInitAppKit(); - initAppKit({ themeMode: 'dark' }); - const metadata = metadataPassed(); - expect(metadata.redirect).toEqual(expect.objectContaining({ universal: expect.any(String) })); - expect(metadata.name).toBe('FxBlox'); - }); - - it('in debug mode, omits the return redirect so the wallet leaves the user where they are', async () => { - // A diagnostic: MetaMask was found already wedged on its splash screen after it sent the user back from - // a pairing approval, before any deep link reached it. Without a redirect it does not send them back, - // and the next log says whether it is still healthy when they switch to it by hand. - useSettingsStore.setState({ - debugMode: { uniqueId: 'dbg', endDate: new Date(Date.now() + 24 * 60 * 60 * 1000) }, - }); - const initAppKit = await freshInitAppKit(); - initAppKit({ themeMode: 'dark' }); - const metadata = metadataPassed(); - expect('redirect' in metadata).toBe(false); - // Everything else the wallet shows the user is untouched. - expect(metadata.name).toBe('FxBlox'); - expect(metadata.url).toEqual(expect.any(String)); - }); - - it('an expired debug mode counts as off', async () => { - useSettingsStore.setState({ - debugMode: { uniqueId: 'dbg', endDate: new Date(Date.now() - 1000) }, - }); - const initAppKit = await freshInitAppKit(); - initAppKit({ themeMode: 'dark' }); - expect('redirect' in metadataPassed()).toBe(true); - }); -}); diff --git a/apps/fxblox-web/src/wallet/appkit.ts b/apps/fxblox-web/src/wallet/appkit.ts index bc8c410..f7c0900 100644 --- a/apps/fxblox-web/src/wallet/appkit.ts +++ b/apps/fxblox-web/src/wallet/appkit.ts @@ -6,9 +6,7 @@ import { createAppKit, type AppKit } from '@reown/appkit/react'; import { Ethers5Adapter } from '@reown/appkit-adapter-ethers5'; import { env } from '@/config/env'; -import { isDebugModeActive, useSettingsStore } from '@/stores/useSettingsStore'; import { APPKIT_NETWORKS, providerMetadata, skaleEuropaHub } from './chains'; -import { diag } from './diag'; let instance: AppKit | null = null; @@ -16,37 +14,14 @@ export interface InitAppKitOptions { themeMode?: 'light' | 'dark'; } -/** - * The metadata handed to the wallet in the session proposal — with one debug-mode difference. - * - * `providerMetadata.redirect` tells the wallet where to send the user back to after they approve (see - * chains.ts). A diagnostic log from the reporter's phone showed MetaMask already wedged on its splash screen - * twelve seconds after it approved a pairing and sent the user back — before any deep link was sent to it, on - * a plain resume from recent apps. Whatever puts it in that state happens on the way it returns the user, and - * the one thing here that makes it return the user is that redirect. So with debug mode on it is omitted: the - * wallet leaves the user where they are after approving, they switch back by hand, and the log says whether - * the wallet is then still healthy when they switch to it for the signature. Debug mode is an explicit opt-in - * used for exactly this kind of report; the default is unchanged. - * - * Decided here, not at the call sites, because there are two (LinkPassword's loader and WalletGate) and the - * first to run wins. AppKit reads the metadata once, so a change of debug mode needs a page reload to apply. - */ -function metadataForInit(): typeof providerMetadata | Omit { - if (!isDebugModeActive(useSettingsStore.getState().debugMode)) return providerMetadata; - const { redirect: _omitted, ...withoutRedirect } = providerMetadata; - return withoutRedirect; -} - export function initAppKit(opts: InitAppKitOptions = {}): AppKit { if (instance) return instance; - const metadata = metadataForInit(); - diag(`[wallet] AppKit init — return redirect ${'redirect' in metadata ? 'on' : 'OFF (debug mode)'}`); instance = createAppKit({ adapters: [new Ethers5Adapter()], networks: APPKIT_NETWORKS, defaultNetwork: skaleEuropaHub, projectId: env.REOWN_PROJECT_ID, - metadata, + metadata: providerMetadata, features: { analytics: false, email: false,