2026-06-02 00:28:22 +08:00
|
|
|
import { requestWalletNonce, verifyWalletSignature } from "./api";
|
|
|
|
|
|
2026-06-02 02:58:01 +08:00
|
|
|
export type WalletKind = "tokenPocket" | "metaMask" | "imToken";
|
|
|
|
|
|
2026-06-02 00:28:22 +08:00
|
|
|
export type EthereumProvider = {
|
2026-06-02 02:58:01 +08:00
|
|
|
isMetaMask?: boolean;
|
|
|
|
|
isTokenPocket?: boolean;
|
|
|
|
|
isImToken?: boolean;
|
|
|
|
|
providers?: EthereumProvider[];
|
2026-06-02 00:28:22 +08:00
|
|
|
request: <T = unknown>(args: {
|
|
|
|
|
method: string;
|
|
|
|
|
params?: unknown[];
|
|
|
|
|
}) => Promise<T>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function getInjectedEthereum(): EthereumProvider | null {
|
|
|
|
|
if (typeof window === "undefined") return null;
|
|
|
|
|
const maybeWindow = window as typeof window & { ethereum?: EthereumProvider };
|
|
|
|
|
return maybeWindow.ethereum ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 02:58:01 +08:00
|
|
|
export function getInjectedWallet(kind?: WalletKind): EthereumProvider | null {
|
|
|
|
|
const ethereum = getInjectedEthereum();
|
|
|
|
|
if (!ethereum || !kind) return ethereum;
|
|
|
|
|
const providers = ethereum.providers?.length
|
|
|
|
|
? ethereum.providers
|
|
|
|
|
: [ethereum];
|
|
|
|
|
const match = providers.find((provider) => {
|
|
|
|
|
if (kind === "metaMask") return provider.isMetaMask;
|
|
|
|
|
if (kind === "tokenPocket") return provider.isTokenPocket;
|
|
|
|
|
if (kind === "imToken") return provider.isImToken;
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
return match ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 10:38:29 +08:00
|
|
|
/** Diagnostic: log what injected providers the browser exposes. */
|
|
|
|
|
export function logWalletProviders(): void {
|
|
|
|
|
const ethereum = getInjectedEthereum();
|
|
|
|
|
const list = (
|
|
|
|
|
ethereum?.providers?.length
|
|
|
|
|
? ethereum.providers
|
|
|
|
|
: ethereum
|
|
|
|
|
? [ethereum]
|
|
|
|
|
: []
|
|
|
|
|
).map((p) => ({
|
|
|
|
|
isMetaMask: Boolean(p.isMetaMask),
|
|
|
|
|
isTokenPocket: Boolean(p.isTokenPocket),
|
|
|
|
|
isImToken: Boolean(p.isImToken),
|
|
|
|
|
}));
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.info("[wallet-login] providers", {
|
|
|
|
|
hasEthereum: Boolean(ethereum),
|
|
|
|
|
count: list.length,
|
|
|
|
|
list,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 02:58:01 +08:00
|
|
|
export async function signInWithInjectedWallet(kind?: WalletKind): Promise<{
|
2026-06-02 00:28:22 +08:00
|
|
|
token: string;
|
|
|
|
|
wallet: string;
|
|
|
|
|
}> {
|
2026-06-02 10:38:29 +08:00
|
|
|
/* eslint-disable no-console */
|
|
|
|
|
console.info("[wallet-login] start injected", { kind });
|
|
|
|
|
logWalletProviders();
|
2026-06-02 02:58:01 +08:00
|
|
|
const ethereum = getInjectedWallet(kind);
|
2026-06-02 10:38:29 +08:00
|
|
|
if (!ethereum) {
|
|
|
|
|
console.warn("[wallet-login] no injected provider found");
|
|
|
|
|
throw new Error("No injected wallet found");
|
|
|
|
|
}
|
2026-06-02 00:28:22 +08:00
|
|
|
|
2026-06-02 03:43:13 +08:00
|
|
|
// Login is signature-only (EIP-191 personal_sign). The backend verifies the
|
|
|
|
|
// recovered address and never inspects chainId, so we deliberately do NOT
|
|
|
|
|
// switch or add any chain — that only adds a failure-prone wallet popup.
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] requesting accounts (eth_requestAccounts)…");
|
2026-06-02 00:28:22 +08:00
|
|
|
const accounts = await ethereum.request<string[]>({
|
|
|
|
|
method: "eth_requestAccounts",
|
|
|
|
|
});
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] accounts", accounts);
|
2026-06-02 00:28:22 +08:00
|
|
|
const address = accounts[0];
|
|
|
|
|
if (!address) throw new Error("No wallet account returned");
|
|
|
|
|
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] requesting nonce for", address);
|
2026-06-02 00:28:22 +08:00
|
|
|
const nonce = await requestWalletNonce(address);
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] got nonce, requesting personal_sign…");
|
2026-06-02 00:28:22 +08:00
|
|
|
const signature = await ethereum.request<string>({
|
|
|
|
|
method: "personal_sign",
|
|
|
|
|
params: [nonce.message, address],
|
|
|
|
|
});
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] signed, verifying with backend…");
|
|
|
|
|
const result = await verifyWalletSignature({
|
2026-06-02 00:28:22 +08:00
|
|
|
address,
|
|
|
|
|
message: nonce.message,
|
|
|
|
|
signature,
|
|
|
|
|
});
|
2026-06-02 10:38:29 +08:00
|
|
|
console.info("[wallet-login] verified, wallet =", result.wallet);
|
|
|
|
|
return result;
|
|
|
|
|
/* eslint-enable no-console */
|
2026-06-02 00:28:22 +08:00
|
|
|
}
|