8.4 KiB
title, type, date, severity, status
| title | type | date | severity | status |
|---|---|---|---|---|
| MetaMask Wallet Login Does Not Surface Address — Debug Report | debug | 2026-06-02 | high | root-caused |
MetaMask Wallet Login Does Not Surface Address — Debug Report
Summary
MetaMask QR login and mobile deeplink login can be approved in MetaMask, but the ARK frontend does not write the approved wallet address into the local wallet session; the mobile QR waiting text is also incorrectly TokenPocket-specific for all wallets.
Expected Behavior
- Selecting MetaMask and approving the connection should result in the page showing the connected wallet address.
- Mobile MetaMask deeplink login should return/reconnect to the page and complete local no-signature login with
local-wallet:<address>. - QR login copy should be generic or absent; it should not say “waiting in TokenPocket” when the selected wallet is MetaMask or imToken.
Actual Behavior
- Terry can approve MetaMask QR/deeplink login, but the web page does not show the authorized address.
- The QR panel uses TokenPocket-specific copy for any wallet on mobile, e.g. imToken QR login still shows a TokenPocket waiting message.
Reproduction Steps
- Open the public frontend and open the wallet login modal.
- Select MetaMask.
- Use either:
- QR login: scan the QR using MetaMask and approve, or
- Mobile app login: tap “Open wallet app”, approve in MetaMask, then return to the web page.
- Observe that the page does not show the wallet address.
- Select imToken QR login on mobile and observe that the QR panel displays TokenPocket-specific waiting text.
Environment
- Project: Arkie Library Frontend (
ark-database-web) - Branch context:
terry-wallet-login - Stack: React 18, Vite, TypeScript, RainbowKit, Wagmi, WalletConnect/Reown
- Wallets involved: MetaMask Mobile, TokenPocket, imToken
- Backend auth endpoints intentionally not required for this flow; login is local no-signature wallet session.
Root Cause Analysis
Failure Chain
WalletLoginModalcallswc.start(kind, mode)for all wallet app/QR flows.useWalletConnectLogin.start()currently chooses the first generic WalletConnect connector for non-injected flows:connectors.find((item) => item.type === "walletConnect") ?? connectors.find((item) => item.id === "walletConnect")
- On MetaMask mobile, RainbowKit’s own MetaMask wallet definition intentionally uses Wagmi’s
metaMask()/ MetaMask SDK connector, not the generic WalletConnect connector. - The custom hook bypasses that MetaMask-specific connector on mobile, so MetaMask SDK deeplink/reconnect handling is not used.
- The hook only calls
completeLogin(localWalletToken(address), address)inside the awaitedconnectAsync(...)result path. - If MetaMask approval completes while the browser is backgrounded, after a page reload, or through a restored Wagmi connection, there is no
useAccount/reconnect bridge that converts the Wagmi connected address into the app’s local wallet session. RainbowWalletProvidercallsuseReconnect(), but this only restores Wagmi connection state; it does not updateWalletProviderunlessuseWalletConnectLoginobserves the restored account and callscompleteLogin.- Therefore MetaMask may be approved/connected at the wallet/Wagmi layer but the ARK UI still has no
local-wallet:<address>token and shows logged-out/no address.
Root Cause
The MetaMask flow is treated as a generic WalletConnect flow, but RainbowKit/Wagmi have MetaMask-specific mobile behavior. Additionally, local ARK wallet login is tied only to the synchronous connectAsync return path instead of being derived from Wagmi account state/reconnect events. This misses MetaMask connections that resolve via mobile app backgrounding, deep link return, QR approval, or page reload.
Evidence
- File:
src/wallet/useWalletConnectLogin.ts— connector selection prefersitem.type === "walletConnect"for all wallets, so mobile MetaMask does not use the RainbowKit/Wagmi MetaMask connector. - File:
src/wallet/useWalletConnectLogin.ts—completeLogin(localWalletToken(...), ...)only runs afterawait connectAsync(...); there is nouseAccounteffect to complete local login when Wagmi is already/reconnected. - File:
src/wallet/RainbowWalletProvider.tsx—WalletReconnectOnMountcallsuseReconnect(), but no downstream code maps the restored Wagmi account intoWalletProvider. - File:
node_modules/@rainbow-me/rainbowkit/dist/wallets/walletConnectors/chunk-BQHQU37S.js— RainbowKit MetaMask wallet usesmetaMask()connector on mobile and comments that “MetaMask mobile deep linking [is] handled by wagmi”. The custom hook bypasses this by selecting a generic WalletConnect connector. - File:
src/wallet/WalletLoginModal.tsx— QR text usesmobileDevice ? t("walletTpWaiting") : t("walletQrUseAnotherDevice")for every selected wallet, causing TokenPocket-specific copy for MetaMask/imToken. - File:
src/locales/en.tsandsrc/locales/zh-CN.ts—walletQrUseAnotherDevicealso explicitly mentions TokenPocket, so even desktop/generic QR copy is wallet-specific.
Affected Files
src/wallet/useWalletConnectLogin.ts— connector choice, deeplink generation, QR URI generation, local-login completion.src/wallet/WalletLoginModal.tsx— misleading QR panel copy.src/wallet/RainbowWalletProvider.tsx— currently reconnects Wagmi but does not by itself complete local login.src/locales/*.ts— QR copy currently contains TokenPocket-specific text.
Suggested Fix
Use the wallet-specific connector when a wallet is selected, especially MetaMask, and add a Wagmi account/reconnect bridge that completes the local wallet session whenever Wagmi has an address. Remove or generalize TokenPocket-specific QR waiting copy.
Fix Strategy
- In
useWalletConnectLogin.ts, prefer a connector matchingpreferredWalletbefore falling back to generic WalletConnect:- MetaMask:
id === "metaMask"ortype === "metaMask" - imToken/TokenPocket: matching wallet id when present, otherwise generic WalletConnect
- MetaMask:
- Add
useAccount()insideuseWalletConnectLoginand auseEffectthat callscompleteLogin(localWalletToken(address), address)when Wagmi reportsisConnected && address. - For MetaMask QR, transform the displayed QR value with the same wallet-specific URI RainbowKit uses:
https://metamask.app.link/wc?uri=<encoded_wc_uri>instead of always rendering rawwc:. - Remove the QR panel paragraph entirely, or replace it with generic copy such as “Please approve the connection in your wallet app.”
- Re-test MetaMask separately for desktop QR scan, mobile Chrome deeplink return, and MetaMask in-app browser injected login.
Risk Assessment
- Risk: Selecting MetaMask’s SDK connector may behave differently from WalletConnect for QR mode. Mitigate by falling back to WalletConnect if the MetaMask connector does not emit a
display_uriin QR mode. - Risk: Auto-completing local login from any Wagmi connected account may log in a stale account after reconnect. Mitigate by only completing when modal/pending/login-in-progress context exists or by clearing stale flags.
- Risk: Changing QR value for MetaMask may affect wallets that scan raw WalletConnect URIs. Mitigate by only applying MetaMask app-link QR transformation for MetaMask.
Verification Plan
- Run
npx tsc --noEmit. - Run
npm run format:check. - Run
npm test. - Desktop Chrome + MetaMask Mobile QR: Select MetaMask → QR login → scan/approve → confirm address and
local-wallet:<address>. - Mobile Chrome + MetaMask app login: Select MetaMask → Open wallet app → approve → return/refresh browser → confirm address appears.
- Regression test TokenPocket and imToken app login.
- Confirm imToken/MetaMask QR login no longer displays TokenPocket-specific text.
Related Issues
- TokenPocket was previously fixed by handling mobile return/reload behavior.
- imToken was previously fixed by adding in-app browser fallback and injected no-signature login.
- Existing local-memory context indicates MetaMask QR approval has been observed to not update frontend state.
Notes
- This report is diagnosis only; no source fix was applied during
/unipi:debug. - The local no-signature session model means the frontend does not need backend wallet nonce/verify endpoints for this fix.
- The current debug UI showing
Wallet debugmay be useful during verification but should be removed or hidden before final production cleanup if no longer needed.