terry-wallet-login #15

Merged
terry merged 95 commits from terry-wallet-login into terry-staging 2026-06-05 16:32:43 +00:00
2 changed files with 46 additions and 31 deletions
Showing only changes of commit e1b24aa0f9 - Show all commits

View File

@@ -146,18 +146,6 @@ export function WalletLoginModal() {
.finally(() => setState("idle")); .finally(() => setState("idle"));
}; };
// Desktop: open the RainbowKit modal — it discovers every installed wallet
// via EIP-6963 (robust when several extensions fight over window.ethereum)
// and also offers a WalletConnect QR. Fall back to the raw injected flow only
// when WalletConnect has no project id configured.
const connectBrowserWallet = () => {
if (wc.available) {
wc.start();
return;
}
void signInjected();
};
const openApp = (kind: WalletKind) => { const openApp = (kind: WalletKind) => {
setError(""); setError("");
setOpeningWallet(kind); setOpeningWallet(kind);
@@ -213,29 +201,20 @@ export function WalletLoginModal() {
</div> </div>
<div className="mt-5 grid gap-3"> <div className="mt-5 grid gap-3">
{/* Browser wallet: in a wallet's in-app browser we sign directly with {/* Browser wallet: sign directly with the injected provider — the
the injected provider; on desktop we open the RainbowKit picker so reliable path for a BNB-chain extension (desktop) or a wallet's
the user can choose among installed extensions reliably. */} in-app browser. No WalletConnect relay involved. */}
{mobileDevice && hasInjected ? ( {!mobileDevice || hasInjected ? (
<button <button
type="button" type="button"
onClick={() => void signInjected()} onClick={() => void signInjected()}
disabled={busy} disabled={busy}
className="flex items-center justify-center gap-2 rounded-2xl bg-ark-gold px-4 py-4 text-base font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70" className="flex items-center justify-center gap-2 rounded-2xl bg-ark-gold px-4 py-4 text-base font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70"
> >
{state === "signing" ? t("walletSigning") : t("walletUseCurrent")} {state === "signing"
</button>
) : !mobileDevice ? (
<button
type="button"
onClick={connectBrowserWallet}
disabled={busy || wc.state !== "idle"}
className="flex items-center justify-center gap-2 rounded-2xl bg-ark-gold px-4 py-4 text-base font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70"
>
{wc.state === "connecting"
? t("walletConnecting")
: wc.state === "signing" || state === "signing"
? t("walletSigning") ? t("walletSigning")
: mobileDevice
? t("walletUseCurrent")
: t("walletInjected")} : t("walletInjected")}
</button> </button>
) : null} ) : null}

View File

@@ -34,30 +34,66 @@ export function getInjectedWallet(kind?: WalletKind): EthereumProvider | null {
return match ?? null; return match ?? null;
} }
/** 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,
});
}
export async function signInWithInjectedWallet(kind?: WalletKind): Promise<{ export async function signInWithInjectedWallet(kind?: WalletKind): Promise<{
token: string; token: string;
wallet: string; wallet: string;
}> { }> {
/* eslint-disable no-console */
console.info("[wallet-login] start injected", { kind });
logWalletProviders();
const ethereum = getInjectedWallet(kind); const ethereum = getInjectedWallet(kind);
if (!ethereum) throw new Error("No injected wallet found"); if (!ethereum) {
console.warn("[wallet-login] no injected provider found");
throw new Error("No injected wallet found");
}
// Login is signature-only (EIP-191 personal_sign). The backend verifies the // Login is signature-only (EIP-191 personal_sign). The backend verifies the
// recovered address and never inspects chainId, so we deliberately do NOT // recovered address and never inspects chainId, so we deliberately do NOT
// switch or add any chain — that only adds a failure-prone wallet popup. // switch or add any chain — that only adds a failure-prone wallet popup.
console.info("[wallet-login] requesting accounts (eth_requestAccounts)…");
const accounts = await ethereum.request<string[]>({ const accounts = await ethereum.request<string[]>({
method: "eth_requestAccounts", method: "eth_requestAccounts",
}); });
console.info("[wallet-login] accounts", accounts);
const address = accounts[0]; const address = accounts[0];
if (!address) throw new Error("No wallet account returned"); if (!address) throw new Error("No wallet account returned");
console.info("[wallet-login] requesting nonce for", address);
const nonce = await requestWalletNonce(address); const nonce = await requestWalletNonce(address);
console.info("[wallet-login] got nonce, requesting personal_sign…");
const signature = await ethereum.request<string>({ const signature = await ethereum.request<string>({
method: "personal_sign", method: "personal_sign",
params: [nonce.message, address], params: [nonce.message, address],
}); });
return verifyWalletSignature({ console.info("[wallet-login] signed, verifying with backend…");
const result = await verifyWalletSignature({
address, address,
message: nonce.message, message: nonce.message,
signature, signature,
}); });
console.info("[wallet-login] verified, wallet =", result.wallet);
return result;
/* eslint-enable no-console */
} }