题目
你需要为一个支持浏览器扩展钱包和 WalletConnect 的多链 DApp 设计连接模块。它应如何处理首次连接、刷新恢复、连接失败、账户切换、账户清空、链切换、错误网络、多个注入钱包和主动断开?
考察目标
- 是否把连接器、连接状态、账户和链建模为不同概念。
- 是否理解 EIP-1193 事件和 EIP-6963 多钱包发现机制。
- 是否能避免刷新闪烁、旧请求污染新账户以及错误网络下发送交易。
- 是否能设计对用户可解释的失败状态。
考察连接恢复、多钱包发现、账户与链变化、错误分类以及界面状态设计。
你需要为一个支持浏览器扩展钱包和 WalletConnect 的多链 DApp 设计连接模块。它应如何处理首次连接、刷新恢复、连接失败、账户切换、账户清空、链切换、错误网络、多个注入钱包和主动断开?
连接模块不应只有一个 isConnected。至少要区分正在恢复、等待用户授权、已连接、断开和失败,并分别保存 connector、address 与 chainId。使用 wagmi 统一管理连接器和 EIP-1193 事件;账户或链变化时,使旧账户查询失效并重新校验目标链。多注入钱包通过 EIP-6963 发现。用户拒绝、未授权、钱包断网和错误网络需要不同提示,断开 DApp 会话也不能假设已经撤销钱包权限。
推荐至少表达以下阶段:
disconnected:没有活动连接。reconnecting:页面加载后尝试恢复曾经的 connector。connecting:已经由用户触发,正在等待钱包或 WalletConnect。connected:存在活动 connector、账户和链。error:连接流程失败,但需要保留具体错误原因。页面刷新时,持久化记录只能说明“上次使用过哪个 connector”,不能直接证明钱包当前仍授权该账户。因此在恢复完成前,不应短暂显示“已连接”或允许发送交易。
交易按钮是否可用,应同时由连接状态、账户、目标链、业务参数和模拟结果决定。
EIP-1193 要求账户变化时触发 accountsChanged,链变化时触发 chainChanged。使用 wagmi 时应优先订阅其连接状态,而不是在多个组件中重复注册原始事件。
账户变化后需要:
链变化后需要:
只读取 window.ethereum 会遇到多个扩展争抢同一个全局对象的问题。EIP-6963 使用窗口事件发现多个 EIP-1193 Provider,并让用户明确选择钱包。生产项目通常通过支持 EIP-6963 的 connector 或连接组件完成发现,不应依赖扩展加载顺序。
钱包提供的名称、图标和 rdns 仍属于自声明信息。尤其是 SVG 图标,需要按规范通过安全方式渲染,不能直接把不可信 SVG 注入 DOM。
至少区分:
4001:用户拒绝请求,允许用户再次主动尝试。4100:当前方法或账户未授权。4900:Provider 与所有链断开。4901:Provider 没有连接目标链。DApp 中的“断开”通常首先清理应用自己的连接状态和会话。钱包权限属于钱包侧能力,除非钱包明确支持相应权限管理方法,否则不能向用户承诺已经撤销授权。
"use client";
import {
useConnect,
useConnection,
useConnectors,
useDisconnect,
useSwitchChain,
} from "wagmi";
export function WalletConnection() {
const connection = useConnection();
const connectors = useConnectors();
const { connect, error: connectError } = useConnect();
const { disconnect } = useDisconnect();
const { switchChain, isPending: isSwitching } = useSwitchChain();
if (!connection.isConnected) {
return connectors.map((connector) => (
<button key={connector.uid} onClick={() => connect({ connector })}>
连接 {connector.name}
</button>
));
}
const isWrongChain = connection.chainId !== 1;
return (
<section>
<p>{connection.address}</p>
{isWrongChain && (
<button
disabled={isSwitching}
onClick={() => switchChain({ chainId: 1 })}
>
切换到以太坊主网
</button>
)}
<button onClick={() => disconnect()}>断开</button>
{connectError && <p role="alert">{connectError.message}</p>}
</section>
);
}
示例只展示状态关系。实际产品还需处理恢复阶段、错误翻译、请求竞态和目标链配置。
window.ethereum,让多个扩展的选择结果不可预测。disconnect() 描述成撤销钱包中的全部授权。rdns 不能作为绝对可信的钱包身份?发现这道题有问题? 反馈此题