题目
DApp 把上次使用的钱包、地址和 chainId 存进 localStorage,刷新后立即显示“已连接”,但钱包已经锁定、撤销授权或切换账户。所谓自动重连应该恢复哪些信息?为什么连接器恢复、账户授权和站内登录是三件不同的事?
考察目标
- 是否能区分缓存线索、可用 Provider、钱包账户授权和服务端登录会话。
- 是否能设计不主动弹窗的启动恢复流程。
- 是否会在账户、链或连接变化后使签名、缓存和服务端身份失效。
考察连接器线索、Provider 会话、账户授权与服务端身份之间的边界和恢复顺序。
DApp 把上次使用的钱包、地址和 chainId 存进 localStorage,刷新后立即显示“已连接”,但钱包已经锁定、撤销授权或切换账户。所谓自动重连应该恢复哪些信息?为什么连接器恢复、账户授权和站内登录是三件不同的事?
localStorage 只能保存“上次用哪个 connector、可能的 Session 标识和 UI 偏好”,不能证明当前地址仍获授权。启动时先恢复连接器实例:注入钱包用静默的 eth_accounts 和 eth_chainId 读取当前可见状态,不能自动调用会弹窗的 eth_requestAccounts;WalletConnect 则检查已有 Session 是否未过期且 namespaces 仍覆盖需要的账户、链和方法。拿到当前账户后才能进入钱包已连接态。站内登录还要单独验证服务端 cookie 或重新执行带 nonce 的签名认证。若地址或链变化,应让相关链上缓存、待签名操作和绑定旧地址的服务端身份失效。
三者可以独立失效。浏览器记住上次选了 MetaMask,不代表扩展仍安装;WalletConnect topic 存在不代表 Session 未过期;钱包仍连接也不代表站内登录 cookie 有效。
页面启动时可以先渲染“正在恢复连接”,然后:
accountsChanged、chainChanged 和 disconnect。(connector, account, chainId) 重建客户端和缓存键。自动恢复不应该在页面加载时触发授权弹窗。eth_requestAccounts 应由用户动作触发;否则浏览器可能拦截,也会造成侵入式体验。
账户变化后,应清理或隔离:
链变化后,应重建 RPC/合约上下文并让链相关查询失效。已经广播的交易仍属于原 chainId,应继续按原链跟踪,不能因为钱包切链就改写归属。
地址是公开标识,不是登录凭证。即使缓存地址与钱包当前账户相同,也不能用它直接恢复认证。服务端要么验证仍有效的安全会话 cookie,要么下发一次性 nonce 让用户重新签名,并校验 domain、URI、chainId、时间和 nonce 等上下文。
async function restoreInjected(provider: Eip1193Provider) {
const accounts = await provider.request({ method: "eth_accounts" });
const chainId = await provider.request({ method: "eth_chainId" });
if (!Array.isArray(accounts) || accounts.length === 0) {
return { status: "disconnected" } as const;
}
return {
status: "connected",
account: accounts[0],
chainId,
} as const;
}
示例刻意不调用 eth_requestAccounts,其中 Eip1193Provider 表示项目对 request 接口的类型定义。实际代码还应验证返回值、注册事件并在卸载时清理监听器。
eth_requestAccounts。eth_accounts 适合静默恢复而 eth_requestAccounts 不适合?发现这道题有问题? 反馈此题