From 526facb26188bf7a7d835f27be6d77dada5eccd6 Mon Sep 17 00:00:00 2001 From: TerryM Date: Thu, 4 Jun 2026 07:23:05 +0800 Subject: [PATCH] fix: require signature for tokenpocket direct login --- ...-tokenpocket-signature-verification-fix.md | 34 +++++++++++++++++++ src/wallet/AutoInjectedLogin.tsx | 13 +++---- src/wallet/useWalletConnectLogin.ts | 21 +++++++----- 3 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 .unipi/docs/fix/2026-06-04-tokenpocket-signature-verification-fix.md diff --git a/.unipi/docs/fix/2026-06-04-tokenpocket-signature-verification-fix.md b/.unipi/docs/fix/2026-06-04-tokenpocket-signature-verification-fix.md new file mode 100644 index 0000000..a224268 --- /dev/null +++ b/.unipi/docs/fix/2026-06-04-tokenpocket-signature-verification-fix.md @@ -0,0 +1,34 @@ +--- +title: "TokenPocket direct login requires signature verification — Quick Fix" +type: quick-fix +date: 2026-06-04 +--- + +# TokenPocket direct login requires signature verification — Quick Fix + +## Bug + +Mobile TokenPocket deeplink opened the site inside the wallet browser and completed login immediately after reading the injected wallet address. It did not trigger a password/signature verification step, so users did not get an explicit address verification prompt. + +## Root Cause + +`AutoInjectedLogin` used `connectInjectedWallet()` and then wrote a local frontend wallet token. The injected deeplink path in `useWalletConnectLogin` used the same address-only flow. Both paths skipped the existing backend nonce + `personal_sign` verification flow. + +## Fix + +Changed injected wallet direct login to use `signInWithInjectedWallet()`, which requests a backend nonce, asks the wallet to sign it, verifies the signature with the backend, and stores the verified backend JWT. If injected verification fails, the direct injected path now stops with an error instead of falling back to an unverified WalletConnect/local-token login. + +### Files Modified + +- `src/wallet/AutoInjectedLogin.tsx` — TP/imToken `?autoLogin=` deeplink now requires wallet signature verification before completing login. +- `src/wallet/useWalletConnectLogin.ts` — injected deeplink path now uses verified sign-in and does not bypass verification after a signature failure. + +## Verification + +- `npx tsc --noEmit` +- `npm run format:check` +- `npm test` + +## Notes + +WalletConnect QR fallback still uses the existing local-session behavior; this fix targets the TokenPocket/injected direct-login flow described in the bug report. diff --git a/src/wallet/AutoInjectedLogin.tsx b/src/wallet/AutoInjectedLogin.tsx index dfc5178..9a51002 100644 --- a/src/wallet/AutoInjectedLogin.tsx +++ b/src/wallet/AutoInjectedLogin.tsx @@ -1,10 +1,10 @@ import { useEffect } from "react"; import { - connectInjectedWallet, getInjectedWallet, + signInWithInjectedWallet, type WalletKind, } from "./injected"; -import { localWalletToken, useWallet } from "./WalletProvider"; +import { useWallet } from "./WalletProvider"; const AUTO_LOGIN_PARAM = "autoLogin"; const ETHEREUM_WAIT_MS = 8000; @@ -46,8 +46,9 @@ function waitForInjected(kind: WalletKind): Promise { /** * When the page is opened via a `?autoLogin=` deeplink (typically from * inside TokenPocket / imToken in-app browsers), wait for the wallet to inject - * `window.ethereum`, then complete a local wallet session automatically. Bypasses - * WalletConnect entirely so it works on networks where the WC relay is blocked. + * `window.ethereum`, then require a wallet signature and complete a verified + * backend wallet session. Bypasses WalletConnect entirely so it works on + * networks where the WC relay is blocked. */ export function AutoInjectedLogin() { const { completeLogin, status } = useWallet(); @@ -65,9 +66,9 @@ export function AutoInjectedLogin() { void waitForInjected(kind).then(async (ready) => { if (cancelled || !ready) return; try { - const address = await connectInjectedWallet(kind); + const res = await signInWithInjectedWallet(kind); if (cancelled) return; - completeLogin(localWalletToken(address), address); + completeLogin(res.token, res.wallet); } catch (err) { // eslint-disable-next-line no-console console.warn("[wallet-autologin] failed", err); diff --git a/src/wallet/useWalletConnectLogin.ts b/src/wallet/useWalletConnectLogin.ts index 5e6c3be..849c17c 100644 --- a/src/wallet/useWalletConnectLogin.ts +++ b/src/wallet/useWalletConnectLogin.ts @@ -3,8 +3,8 @@ import { useAccount, useConnect, useDisconnect } from "wagmi"; import { bsc } from "wagmi/chains"; import { hasWalletConnectProjectId } from "./RainbowWalletProvider"; import { - connectInjectedWallet, getInjectedWallet, + signInWithInjectedWallet, type WalletKind, } from "./injected"; import { localWalletToken, useWallet } from "./WalletProvider"; @@ -98,8 +98,8 @@ function connectorMatchesWallet( * MetaMask / imToken QR fallback via RainbowKit + WalletConnect. * * Flow: connect through RainbowKit/Wagmi on BNB Chain -> once an account is - * connected, complete a local frontend wallet session. No message signature, - * backend nonce, or verify call is required. + * connected, complete a local frontend wallet session. WalletConnect fallback + * does not require message signature, backend nonce, or verify call. * * Entirely gated behind a real `VITE_WALLETCONNECT_PROJECT_ID`: when it is * missing `available` is false and `start` is a no-op, so callers can hide or @@ -177,21 +177,26 @@ export function useWalletConnectLogin() { getInjectedWallet(preferredWallet) ) { try { - const injectedAddress = await connectInjectedWallet(preferredWallet); - console.info("[wallet-login] injected connected", { + setState("signing"); + const result = await signInWithInjectedWallet(preferredWallet); + console.info("[wallet-login] injected verified", { preferredWallet, - address: injectedAddress, + address: result.wallet, chain: "BNB Chain", chainId: bsc.id, }); - completeLogin(localWalletToken(injectedAddress), injectedAddress); + completeLogin(result.token, result.wallet); setState("idle"); return; } catch (err) { - console.info("[wallet-login] injected connect fallback to wc", { + pendingRef.current = false; + setState("idle"); + setError(err instanceof Error ? err.message : "Wallet login failed"); + console.info("[wallet-login] injected verification failed", { preferredWallet, message: err instanceof Error ? err.message : String(err), }); + return; } }