terry-wallet-login #15

Merged
terry merged 95 commits from terry-wallet-login into terry-staging 2026-06-05 16:32:43 +00:00
5 changed files with 54 additions and 162 deletions
Showing only changes of commit 93790cb885 - Show all commits

View File

@@ -0,0 +1,37 @@
---
title: "Remove wallet address verification popup — Quick Fix"
type: quick-fix
date: 2026-06-04
---
# Remove wallet address verification popup — Quick Fix
## Bug
The in-app-browser wallet address verification popup added friction and interfered with imToken login. The requested behavior is to remove that popup and keep the deeplink login flow direct.
## Root Cause
`AutoInjectedLogin` rendered a blocking confirmation dialog for `?autoLogin=` deeplink sessions before calling the injected wallet signature flow. That UI was unnecessary for the current wallet-login flow and could block or confuse imToken users.
## Fix
Removed the verification dialog and restored direct deeplink behavior: after the wallet in-app browser injects `window.ethereum`, the app calls `signInWithInjectedWallet()` and completes backend-verified login. Existing logged-in sessions still skip auto-login after stripping the deeplink parameter.
### Files Modified
- `src/wallet/AutoInjectedLogin.tsx` — removes the verification popup UI and auto-runs signature login for logged-out deeplink sessions.
- `src/wallet/injected.ts` — removes now-unused connected-address helper.
- `src/locales/zh-CN.ts` — removes unused verification popup copy.
- `src/locales/en.ts` — removes unused verification popup copy.
## Verification
- `rg -n "walletVerifyAddress|walletDetectedAddress|getConnectedInjectedAddress|wallet-verify" src || true`
- `npx tsc --noEmit`
- `npm run format:check`
- `npm test`
## Notes
The imToken injected-provider fallback remains in place; only the confirmation popup and its supporting copy/helper were removed.

View File

@@ -197,13 +197,6 @@ export const enDict: Dict = {
walletTpLoginBtn: "Log in with TokenPocket", walletTpLoginBtn: "Log in with TokenPocket",
walletTpWaiting: "Waiting for your signature in TokenPocket…", walletTpWaiting: "Waiting for your signature in TokenPocket…",
walletTpReopen: "Reopen TokenPocket", walletTpReopen: "Reopen TokenPocket",
walletVerifyAddressTitle: "Verify wallet address",
walletVerifyAddressDesc:
"Confirm the wallet address you want to use. After you tap the button below, your wallet app will ask you to sign and verify the address.",
walletDetectedAddress: "Detected wallet address",
walletVerifyAddressUnknown:
"The wallet address will be requested after verification",
walletVerifyAddressButton: "Verify this address",
favoritesFilters: "Filters", favoritesFilters: "Filters",
favoriteSessionExpired: "Your session expired. Please sign in again.", favoriteSessionExpired: "Your session expired. Please sign in again.",
loadFailed: "Could not load your favorites.", loadFailed: "Could not load your favorites.",

View File

@@ -186,12 +186,6 @@ export const zhDict: Dict = {
walletTpLoginBtn: "使用 TokenPocket 登录", walletTpLoginBtn: "使用 TokenPocket 登录",
walletTpWaiting: "等待你在 TokenPocket 中完成签名…", walletTpWaiting: "等待你在 TokenPocket 中完成签名…",
walletTpReopen: "重新打开 TokenPocket", walletTpReopen: "重新打开 TokenPocket",
walletVerifyAddressTitle: "验证钱包地址",
walletVerifyAddressDesc:
"请确认将使用当前钱包地址登录。点击下方按钮后,钱包 App 会要求你签名验证地址。",
walletDetectedAddress: "检测到的钱包地址",
walletVerifyAddressUnknown: "点击验证后将请求钱包地址",
walletVerifyAddressButton: "验证此地址",
favoritesFilters: "筛选", favoritesFilters: "筛选",
favoriteSessionExpired: "登录已过期,请重新登录。", favoriteSessionExpired: "登录已过期,请重新登录。",
loadFailed: "无法加载收藏,请稍后重试。", loadFailed: "无法加载收藏,请稍后重试。",

View File

@@ -1,25 +1,15 @@
import { useCallback, useEffect, useState } from "react"; import { useEffect } from "react";
import { useI18n } from "../i18n";
import { import {
getConnectedInjectedAddress,
getInjectedWallet, getInjectedWallet,
signInWithInjectedWallet, signInWithInjectedWallet,
type WalletKind, type WalletKind,
} from "./injected"; } from "./injected";
import { shortenAddress, useWallet } from "./WalletProvider"; import { useWallet } from "./WalletProvider";
const AUTO_LOGIN_PARAM = "autoLogin"; const AUTO_LOGIN_PARAM = "autoLogin";
const ETHEREUM_WAIT_MS = 8000; const ETHEREUM_WAIT_MS = 8000;
const ETHEREUM_POLL_MS = 200; const ETHEREUM_POLL_MS = 200;
type AutoLoginRequest = {
kind: WalletKind;
ready: boolean;
address: string | null;
signing: boolean;
error: string;
};
function parseKind(value: string | null): WalletKind | null { function parseKind(value: string | null): WalletKind | null {
if (value === "tokenPocket" || value === "metaMask" || value === "imToken") { if (value === "tokenPocket" || value === "metaMask" || value === "imToken") {
return value; return value;
@@ -55,15 +45,13 @@ function waitForInjected(kind: WalletKind): Promise<boolean> {
/** /**
* When the page is opened via a `?autoLogin=<wallet>` deeplink (typically from * When the page is opened via a `?autoLogin=<wallet>` deeplink (typically from
* inside TokenPocket / imToken in-app browsers), show an explicit verification * inside TokenPocket / imToken in-app browsers), wait for the wallet to inject
* prompt first. The wallet signature and backend verification only start after * `window.ethereum`, then require a wallet signature and complete a verified
* the user taps the verification button, so Chrome -> wallet handoff never logs * backend wallet session. Bypasses WalletConnect entirely so it works on
* in silently from a cached in-app-browser session. * networks where the WC relay is blocked.
*/ */
export function AutoInjectedLogin() { export function AutoInjectedLogin() {
const { t } = useI18n();
const { completeLogin, status } = useWallet(); const { completeLogin, status } = useWallet();
const [request, setRequest] = useState<AutoLoginRequest | null>(null);
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
@@ -73,133 +61,24 @@ export function AutoInjectedLogin() {
if (status === "loading") return; if (status === "loading") return;
stripAutoLoginParam(); stripAutoLoginParam();
if (status === "loggedIn") { if (status === "loggedIn") return;
setRequest(null);
return;
}
let cancelled = false; let cancelled = false;
setRequest({
kind,
ready: false,
address: null,
signing: false,
error: "",
});
void waitForInjected(kind).then(async (ready) => { void waitForInjected(kind).then(async (ready) => {
if (cancelled) return; if (cancelled || !ready) return;
if (!ready) { try {
setRequest((current) => const res = await signInWithInjectedWallet(kind);
current?.kind === kind if (cancelled) return;
? { ...current, ready: false, error: t("walletNoBrowserWallet") } completeLogin(res.token, res.wallet);
: current, } catch (err) {
); // eslint-disable-next-line no-console
return; console.warn("[wallet-autologin] failed", err);
} }
const address = await getConnectedInjectedAddress(kind);
if (cancelled) return;
setRequest((current) =>
current?.kind === kind
? { ...current, ready: true, address, error: "" }
: current,
);
}); });
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [status, t]); }, [completeLogin, status]);
const verifyAddress = useCallback(async () => { return null;
if (!request || !request.ready || request.signing) return;
setRequest((current) =>
current ? { ...current, signing: true, error: "" } : current,
);
try {
const res = await signInWithInjectedWallet(request.kind);
completeLogin(res.token, res.wallet);
setRequest(null);
} catch (err) {
const message =
err instanceof Error ? err.message : t("walletLoginFailed");
setRequest((current) =>
current
? {
...current,
signing: false,
error: message || t("walletLoginFailed"),
}
: current,
);
// eslint-disable-next-line no-console
console.warn("[wallet-autologin] verification failed", err);
}
}, [completeLogin, request, t]);
if (!request) return null;
const addressLabel = request.address
? shortenAddress(request.address)
: t("walletVerifyAddressUnknown");
return (
<div
className="fixed inset-0 z-[130] flex items-end justify-center bg-black/75 px-3 pb-3 pt-10 backdrop-blur-sm md:items-center md:p-6"
role="dialog"
aria-modal="true"
aria-labelledby="wallet-verify-title"
>
<div className="w-full max-w-[420px] rounded-3xl border border-white/10 bg-[#17171d] p-5 shadow-2xl shadow-black/70 md:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-ark-gold">
{t(walletNameKey(request.kind))}
</p>
<h2
id="wallet-verify-title"
className="mt-3 text-xl font-semibold text-white"
>
{t("walletVerifyAddressTitle")}
</h2>
<p className="mt-2 text-sm leading-6 text-neutral-400">
{t("walletVerifyAddressDesc")}
</p>
<div className="mt-5 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
<p className="text-xs text-neutral-500">
{t("walletDetectedAddress")}
</p>
<p className="mt-1 break-all font-mono text-sm text-neutral-100">
{addressLabel}
</p>
</div>
{request.error ? (
<p className="mt-4 rounded-2xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-200">
{request.error}
</p>
) : null}
<button
type="button"
onClick={verifyAddress}
disabled={!request.ready || request.signing}
className="mt-5 w-full rounded-full bg-ark-gold px-4 py-3 text-sm font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70"
>
{request.signing
? t("walletSigning")
: request.ready
? t("walletVerifyAddressButton")
: t("walletConnecting")}
</button>
</div>
</div>
);
}
function walletNameKey(kind: WalletKind): string {
if (kind === "tokenPocket") return "walletTokenPocket";
if (kind === "metaMask") return "walletMetaMask";
return "walletImToken";
} }

View File

@@ -166,17 +166,6 @@ export function getInjectedWallet(kind?: WalletKind): EthereumProvider | null {
return null; return null;
} }
export async function getConnectedInjectedAddress(
kind?: WalletKind,
): Promise<string | null> {
const ethereum = getInjectedWallet(kind);
if (!ethereum) return null;
const accounts = await ethereum
.request<unknown[]>({ method: "eth_accounts" })
.catch((): unknown[] => []);
return accounts.find(isAddress) ?? null;
}
/** Diagnostic: log what injected providers the browser exposes. */ /** Diagnostic: log what injected providers the browser exposes. */
export function logWalletProviders(): void { export function logWalletProviders(): void {
const ethereum = getInjectedEthereum(); const ethereum = getInjectedEthereum();