3、WalletConnect集成:协议原理、QR Code配对、Session建立、发送交易、断开连接、多链支持
WalletConnect,说白了就是让DApp和手机钱包「隔空握手」的协议。我最早接触它是在2020年,那时候大家还在用MetaMask浏览器插件,移动端体验简直灾难。后来WalletConnect出现,才真正打通了桌面端和移动端的壁垒。
嗯,咱们直接进入正题。这一章我会把WalletConnect的集成拆成六个核心环节来讲:协议原理、QR Code配对、Session建立、发送交易、断开连接、多链支持。每个环节我都会结合自己踩过的坑来聊。
3.1 协议原理:它到底是怎么工作的?
WalletConnect不是点对点直连,而是通过一个「中继桥」来转发消息。你想想看,手机和电脑不在同一个局域网,怎么通信?靠的就是这个中继服务器。
核心流程其实很简单:
- DApp端:生成一个配对URI,包含一个唯一的Session ID
- 钱包端:扫描二维码,解析出URI,然后通过中继桥建立连接
- 中继桥:负责转发加密后的JSON-RPC消息
这里有个关键点——所有消息都是端到端加密的。中继服务器只能看到「谁在跟谁说话」,但看不到「说了什么」。我在项目中遇到过有人担心隐私问题,其实大可不必,协议层已经帮你兜底了。
协议栈速览:
- 传输层:WebSocket(中继桥)
- 加密层:对称加密(Session Key)
- 消息层:JSON-RPC 2.0
- 应用层:EIP-155(交易格式)
3.2 QR Code配对:从生成到扫描
配对的第一步,是DApp生成一个二维码。这个二维码里藏的是什么?是一个标准的WalletConnect URI,格式长这样:
wc:<topic>@<version>?bridge=<bridge_url>&key=<symmetric_key>
我来拆解一下:
topic:随机生成的Session ID,用于标识这次连接version:协议版本,目前主流是2.0bridge:中继服务器地址,默认是官方提供的key:对称加密密钥,用于后续消息加密
生成二维码的代码,我个人习惯用qrcode.react这个库:
import QRCode from 'qrcode.react';
function WalletConnectQR({ uri }) {
return (
<div className="qr-container">
<QRCode value={uri} size={256} />
<p>请用钱包扫描此二维码</p>
</div>
);
}
小技巧:二维码尺寸建议不小于200px,否则手机摄像头可能识别不了。我曾经因为二维码太小,被测试同事吐槽了整整一下午。
3.3 Session建立:握手与确认
二维码扫完之后,钱包和DApp之间会进行一轮「握手」。这个过程大概分三步:
- 钱包发送Session Proposal:钱包告诉DApp「我支持这些链、这些方法」
- DApp响应Session Approval:DApp确认「好的,我们开始吧」
- 双方交换Session Key:后续所有消息都用这个Key加密
代码层面,用@walletconnect/web3-provider这个库可以快速搞定:
import WalletConnectProvider from '@walletconnect/web3-provider';
const provider = new WalletConnectProvider({
infuraId: '你的INFURA_ID', // 用于RPC调用
qrcode: false, // 我们自己生成二维码,所以关掉内置的
});
// 建立连接
await provider.enable();
// 此时Session已经建立,可以拿到accounts和chainId
嗯,这里要注意:provider.enable()会弹出一个二维码窗口(如果你没关掉内置的)。我建议把qrcode设为false,然后自己控制UI展示,体验会好很多。
避坑指南:我曾经遇到过Session建立后突然断开的情况,排查了半天发现是中继服务器超时。解决方案是自定义bridge地址,用自己部署的中继服务,稳定性会高很多。
3.4 发送交易:签名与广播
Session建立之后,就可以发送交易了。流程其实跟MetaMask差不多:
- DApp构造交易对象
- 通过Provider发送
eth_sendTransaction - 钱包弹出确认界面,用户签名
- 签名后的交易广播到链上
代码示例:
const tx = {
from: accounts[0],
to: '0x...',
value: ethers.utils.parseEther('0.1'),
gasLimit: 21000,
};
// 发送交易
const txHash = await provider.send('eth_sendTransaction', [tx]);
console.log('交易哈希:', txHash);
你想想看,这里有个细节——gasLimit和gasPrice要不要自己算?我个人建议让钱包端来处理,因为不同钱包的gas策略不一样。你硬塞一个值过去,反而可能出问题。
签名类型:
eth_sendTransaction:发送交易(需要gas)eth_sign:普通签名(不消耗gas)personal_sign:带消息前缀的签名(推荐)eth_signTypedData:结构化数据签名(EIP-712)
3.5 断开连接:优雅地结束会话
断开连接看似简单,但处理不好会留下「僵尸Session」。我见过不少DApp,用户断开后重新连接,结果因为旧Session没清理,导致各种奇怪错误。
正确的断开方式:
// 主动断开
await provider.disconnect();
// 监听断开事件
provider.on('disconnect', (code, reason) => {
console.log('连接已断开:', code, reason);
// 清理本地状态
setAccounts([]);
setConnected(false);
});
为什么要监听disconnect事件?因为用户可能在钱包端主动断开,这时候DApp需要同步更新UI。我曾经没加这个监听,用户断开后界面还显示「已连接」,被用户骂了一顿。
注意:断开后一定要清除本地存储的Session数据,否则下次连接时可能会复用旧的Session,导致配对失败。
3.6 多链支持:一个Session,多条链
WalletConnect 2.0最大的改进之一,就是原生支持多链。一个Session可以同时连接以太坊、Polygon、BNB Chain等。
配置方式很简单,在初始化Provider时指定支持的链:
const provider = new WalletConnectProvider({
rpc: {
1: 'https://mainnet.infura.io/v3/你的ID', // 以太坊
137: 'https://polygon-rpc.com', // Polygon
56: 'https://bsc-dataseed.binance.org', // BSC
},
chainId: 1, // 默认链
});
切换链的时候,调用wallet_switchEthereumChain:
await provider.send('wallet_switchEthereumChain', [
{ chainId: '0x89' } // 137的十六进制
]);
这里有个坑——有些钱包不支持动态切换链。我遇到过用户反馈「点了切换没反应」,后来发现是钱包版本太旧。解决方案是在UI上加个提示,让用户手动在钱包里切换。
多链最佳实践:
- 初始化时列出所有支持的链,不要只列一条
- 切换链前先检查钱包是否支持
wallet_switchEthereumChain - 如果不支持,回退到
wallet_addEthereumChain添加链
总结
WalletConnect的集成,说白了就是「配对-握手-通信-断开」这四个步骤。我个人觉得最难的部分不是写代码,而是处理各种边界情况——用户扫码后取消、网络断开、Session过期……这些都需要你提前想好。
嗯,这一章的内容就到这里。下一章我们会聊硬件钱包的集成,那又是另一番天地了。
公众号:蓝海资料掘金营,微信deep3321