背景
上个月,我接手了一个新的DeFi聚合器项目的前端重构。这个项目的老前端用的是web3modal + 自定义的链配置,代码已经有点“祖传”的味道了,每次加一条新链都得手动改好几个配置文件,测试起来也麻烦。产品经理提了新需求:要快速支持Arbitrum、Optimism、Polygon等七八条EVM链,并且用户切换链的体验要足够丝滑。
我评估了一下,自己从头用wagmi去搭一套连接组件,虽然灵活,但时间成本太高,光是设计UI和处理好各种边缘情况(比如用户钱包里没添加该链)就得花上好几天。这时候,我想到了RainbowKit——一个基于wagmi构建的、开箱即用的钱包连接套件,UI漂亮,文档说支持多链配置。心想,用它应该能快速搞定,把时间省下来去处理更复杂的业务逻辑。于是,我的“快速集成”之旅开始了,没想到,快是快了,坑也是一个没少踩。
问题分析
一开始,我的思路很简单:照着RainbowKit官方文档的“Getting Started”部分,安装依赖,用getDefaultConfig搞个配置,把RainbowKitProvider和WagmiProvider一套,最后把ConnectButton一扔,不就完事了吗?我最初的核心配置代码是这样的:
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { mainnet, polygon, optimism, arbitrum } from 'wagmi/chains';
const config = getDefaultConfig({
appName: 'My DeFi App',
projectId: 'YOUR_PROJECT_ID', // 从WalletConnect Cloud拿的
chains: [mainnet, polygon, optimism, arbitrum],
});
function App() {
return (
<WagmiProvider config={config}>
<RainbowKitProvider>
<ConnectButton />
</RainbowKitProvider>
</WagmiProvider>
);
}
跑起来一看,连接MetaMask确实没问题,主网也能用。但当我尝试切换到Polygon时,问题来了。点击切换,钱包弹窗倒是出来了,但要么是提示“未添加网络”,要么是切换后前端的链ID显示还是1(以太坊主网)。控制台里时不时飘过一些关于RPC URL的警告。
我意识到,问题出在链的配置上。getDefaultConfig和从wagmi/chains导入的链定义,其RPC端点可能是公共的,有速率限制或不稳定。而且,对于用户钱包里没有的链,RainbowKit的默认行为可能和我想的不一样。我需要更精细地控制每条链的配置,特别是RPC,并且要处理好钱包添加网络的流程。这不是一个“五分钟集成”就能完事的问题,需要深入配置。
核心实现
第一步:自定义链配置,搞定稳定的RPC
公共RPC是第一个坑。尤其是在测试网或者Polygon这类链上,公共RPC经常不稳定,导致交易发送失败或者读取数据超时。我的解决方案是使用项目自己的Infura或Alchemy节点,如果没有,也可以选择一些更可靠的公共服务商如publicnode.com。
这里有个关键点:RainbowKit(或者说底层的wagmi v2)的链配置对象,需要包含rpcUrls字段,并且要正确区分default和public。我一开始没注意,直接覆盖错了,导致钱包连接内部调用还是走了不稳定的节点。
// chains/customChains.ts
import { Chain } from 'wagmi/chains';
// 自定义Polygon链配置
export const customPolygon: Chain = {
id: 137,
name: 'Polygon',
network: 'matic',
nativeCurrency: {
name: 'MATIC',
symbol: 'MATIC',
decimals: 18,
},
rpcUrls: {
// default 和 public 最好都配置,default用于钱包写操作,public用于前端读操作
default: {
http: ['https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY'], // 你的Alchemy或Infura URL
},
public: {
http: ['https://polygon-rpc.com'], // 一个可靠的公共RPC
},
},
blockExplorers: {
default: { name: 'PolygonScan', url: 'https://polygonscan.com' },
},
contracts: {
multicall3: {
address: '0xca11bde05977b3631167028862be2a173976ca11',
blockCreated: 25770160,
},
},
};
// 同理,配置其他链,比如Arbitrum
export const customArbitrum: Chain = {
id: 42161,
name: 'Arbitrum One',
network: 'arbitrum',
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://arb1.arbitrum.io/rpc'],
},
public: {
http: ['https://arb1.arbitrum.io/rpc'],
},
},
blockExplorers: {
default: { name: 'Arbiscan', url: 'https://arbiscan.io' },
},
contracts: {
multicall3: {
address: '0xca11bde05977b3631167028862be2a173976ca11',
blockCreated: 7654707,
},
},
};
第二步:配置RainbowKit与Wagmi
有了自定义的链配置,接下来就是正确创建wagmi的config对象。这里我放弃了getDefaultConfig这个快捷方法,因为它对配置的控制不够细。我改用createConfig手动配置,这样可以明确指定传输层(transport)和连接器。
注意这个细节:wagmi的createConfig需要为每条链单独创建transport。我在这里又踩了个坑,试图用一个transport给所有链用,结果只有主网能正常工作。
// config/wagmiConfig.ts
import { http, createConfig } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { customPolygon, customArbitrum, customOptimism } from '../chains/customChains';
import { getDefaultWallets } from '@rainbow-me/rainbowkit';
// 定义项目支持的链数组
const projectChains = [mainnet, customPolygon, customArbitrum, customOptimism] as const;
// 1. 设置钱包连接器 (RainbowKit提供)
const { connectors } = getDefaultWallets({
appName: 'My DeFi Aggregator',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // 必须去WalletConnect Cloud创建项目获取
chains: projectChains,
});
// 2. 创建Wagmi配置
export const config = createConfig({
chains: projectChains,
transports: {
// 为每条链分别创建transport,使用我们自定义的RPC
[mainnet.id]: http(mainnet.rpcUrls.default.http[0]), // 也可以用你的主网节点
[customPolygon.id]: http(customPolygon.rpcUrls.default.http[0]),
[customArbitrum.id]: http(customArbitrum.rpcUrls.default.http[0]),
[customOptimism.id]: http(customOptimism.rpcUrls.default.http[0]),
},
connectors, // 注入RainbowKit生成的连接器
ssr: false, // 如果不是Next.js等SSR框架,可以设为false
});
第三步:集成到React应用中并实现链切换
配置完成后,在应用根组件中注入Provider就相对简单了。但为了让用户能方便地切换链,我不仅使用了ConnectButton(它自带切换网络的下拉菜单),还在应用内部关键位置(比如资产面板顶部)添加了一个手动的链切换器,使用useSwitchChain这个hook。
这里有个用户体验上的坑:如果用户的钱包里没有添加你指定的链,直接调用switchChain会失败。RainbowKit的ConnectButton下拉菜单会自动处理这个情况(触发钱包添加网络),但自己写的切换器需要手动处理。我的做法是捕获错误,然后调用addChain。
// components/ChainSwitcher.tsx
import { useChainId, useSwitchChain, useChains } from 'wagmi';
import { useCallback } from 'react';
export function ChainSwitcher() {
const currentChainId = useChainId();
const { switchChain } = useSwitchChain();
const supportedChains = useChains();
const handleSwitch = useCallback(async (targetChainId: number) => {
if (targetChainId === currentChainId) return;
try {
await switchChain({ chainId: targetChainId });
} catch (error: any) {
// 错误码 4902 是钱包(如MetaMask)提示用户添加网络的标准错误
if (error?.code === 4902) {
// 在实际项目中,这里应该弹出一个更友好的提示,引导用户去ConnectButton那里切换,或者手动触发addChain。
// 因为addChain API需要完整的链信息,直接从supportedChains里找。
const targetChain = supportedChains.find(c => c.id === targetChainId);
if (targetChain) {
console.warn(`请手动在钱包中添加 ${targetChain.name} 网络,或使用右上角的连接按钮进行切换。`);
// 可以在这里调用 window.ethereum.request({ method: 'wallet_addEthereumChain', params: [targetChainInfo] })
}
}
console.error('切换链失败:', error);
}
}, [currentChainId, switchChain, supportedChains]);
return (
<div className="chain-switcher">
<span>当前网络: </span>
<select
value={currentChainId}
onChange={(e) => handleSwitch(Number(e.target.value))}
>
{supportedChains.map((chain) => (
<option key={chain.id} value={chain.id}>
{chain.name}
</option>
))}
</select>
</div>
);
}
完整代码示例
下面是一个简化但可运行的应用根组件示例,整合了上述所有配置:
// App.tsx
import { WagmiProvider } from 'wagmi';
import { RainbowKitProvider, darkTheme, ConnectButton } from '@rainbow-me/rainbowkit';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './config/wagmiConfig';
import { ChainSwitcher } from './components/ChainSwitcher';
import '@rainbow-me/rainbowkit/styles.css'; // 不要忘记引入样式!
// 为Wagmi的缓存创建QueryClient
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider
theme={darkTheme()} // 可以自定义主题
coolMode // 开启酷炫的按钮效果
locale="en-US" // 设置语言
>
<div className="app">
<header>
<h1>我的DeFi聚合器</h1>
<div className="wallet-section">
<ConnectButton
accountStatus="full" // 显示完整地址
chainStatus="icon" // 只显示链图标,不显示名称
showBalance={false}
/>
</div>
</header>
<main>
<div className="network-panel">
<ChainSwitcher />
</div>
{/* 你的其他业务组件 */}
<div>业务内容区域...</div>
</main>
</div>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
export default App;
踩坑记录
-
projectId无效或缺失导致的静默失败:最开始我没仔细看文档,随便写了个字符串当projectId。结果钱包连接(尤其是WalletConnect)时,移动端扫码后一直连接不上,前端也没明显报错。解决方法:必须去WalletConnect Cloud创建项目,获取真实的projectId。 -
链切换后,前端状态不同步:点击切换链,钱包成功了,但应用里
useChainId()返回的还是旧的链ID。排查发现:这是因为我在不同的地方用了不同的wagmi配置实例,或者Provider包裹层级有问题。解决方法:确保整个应用只用一个config,且WagmiProvider包裹了所有用到wagmi hook的组件。 -
自定义链的图标不显示:RainbowKit为一些主流链内置了图标,但自定义链或一些较新的链(比如Base)可能没有。解决方法:可以通过
RainbowKitProvider的chainImages属性来注入自定义链图标,是一个{ [chainId: number]: string }的映射,值为图片URL。 -
SSR(Next.js)下的水合错误:在Next.js项目里,因为服务端和客户端初始状态可能不一致(比如连接的钱包信息),会导致水合错误。解决方法:RainbowKit提供了
SSRProvider组件来配合Next.js的App Router使用。同时,将wagmi配置中的ssr设为true,并确保连接状态相关的UI在客户端渲染后再显示(用useEffect或useState控制)。
小结
这次集成让我体会到,RainbowKit确实能极大加速Web3应用钱包连接部分的开发,但它不是“无脑”配置就能应对所有生产环境需求的。核心收获是:多链支持的关键在于稳定且可控制的RPC配置,以及对“用户钱包可能未添加链”这一情况的妥善处理。 下一步,可以继续深挖RainbowKit的主题定制、与Zustand/Redux的状态集成,以及如何优雅地处理连接断开和重连的逻辑。