Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 2 additions & 23 deletions apps/fxblox-web/src/components/setup/WalletSigner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -151,12 +150,9 @@ export default function WalletSigner({
const [requestLink, setRequestLink] = useState<string | null>(null);
const requestLinkRef = useRef<string | null>(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);
Expand Down Expand Up @@ -203,7 +199,6 @@ export default function WalletSigner({
setShowNudge(false);
setShowStuckHint(false);
setWalletShowedNothing(false);
setHopSkippedForDebug(false);
wentToWalletRef.current = false;
return undefined;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -418,11 +402,6 @@ export default function WalletSigner({
{t('setup.linkPassword.walletStuckHint')}
</FxText>
)}
{hopSkippedForDebug && (
<FxText variant="bodyXSRegular" color="warningBase" textAlign="center" testID="debug-no-auto-open">
{t('setup.linkPassword.debugNoAutoOpen')}
</FxText>
)}
{showOpenWallet && walletLink && (
<FxButton size="large" onPress={openWallet} testID="open-wallet">
{t('setup.linkPassword.openWalletToApprove')}
Expand Down
5 changes: 2 additions & 3 deletions apps/fxblox-web/src/i18n/locales/en/setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
5 changes: 2 additions & 3 deletions apps/fxblox-web/src/i18n/locales/zh/setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@
"signaturePortalHint": "请在 fxblox.fx.land 完成签名,然后将签名与用于签名的钱包地址一起粘贴到下方。",
"walletConnectedTapSign": "钱包已连接。点击下方按钮进行签名 — 这会再次打开您的钱包。",
"approveInWallet": "请在您的钱包中确认此请求…",
"walletStuckHint": "钱包已打开但停在启动画面?请从最近任务中彻底关闭钱包,然后再次点击“打开钱包进行签名” — 不先关闭钱包而直接再次点击是没有用的。您的签名请求仍然有效。如果每次都这样,说明 Android 正在后台限制钱包:请在 设置 → 应用 → (应用)→ 电池 中,将 MetaMask 和您的浏览器设为“不受限制”,问题即可消除。",
"openWalletToApprove": "打开钱包进行签名",
"debugNoAutoOpen": "调试模式:未自动打开钱包。请求已在中继上等待 — 请从最近任务中切换到您的钱包并记录它显示的内容,或点击“打开钱包进行签名”。"
"walletStuckHint": "钱包已打开但停在启动画面?请从最近任务中彻底关闭钱包,然后重新打开 — 点击钱包图标或点击“打开钱包进行签名”均可 — 它会立即显示签名请求。不先关闭钱包而直接再次打开是没有用的。您的签名请求仍然有效,不会丢失。",
"openWalletToApprove": "打开钱包进行签名"
},
"connectToBlox": {
"intro": "打开您的 Blox,然后通过蓝牙连接。如果不成功,我们会为您提供其他方式。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string>((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();
Expand Down
65 changes: 0 additions & 65 deletions apps/fxblox-web/src/wallet/__tests__/appkit.test.ts

This file was deleted.

27 changes: 1 addition & 26 deletions apps/fxblox-web/src/wallet/appkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,22 @@
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;

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<typeof providerMetadata, 'redirect'> {
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,
Expand Down
Loading