跳到主要内容

React + wagmi

本文演示如何在 React 项目中通过 wagmi 连接 UKey Wallet。整体方案基于 EIP-1193 Provider:应用优先读取 UKey Wallet 的专用注入对象 window.$ukey.ethereum,找不到时再从多钱包 Provider 列表或通用 window.ethereum 中回退选择。

部署

npm i wagmi viem

wagmi v2 使用 viem 作为底层能力。本页采用 Injected connector 连接浏览器注入的钱包,因此既能支持 UKey Wallet,也能兼容其他 EIP-1193 钱包。

创建优先选择 UKey Wallet 的连接器

浏览器里可能同时安装多个钱包扩展。如果直接使用 window.ethereum,应用有时无法确定最终连到哪一个钱包。下面的连接器会先检查 UKey Wallet 专用入口,再按顺序回退,保证连接行为更可预测。

// 参考文件:ukeyConnector.ts
import { createConfig, http } from "wagmi";
import { mainnet, polygon, arbitrum, base, optimism } from "wagmi/chains";
import { injected } from "wagmi/connectors";

function selectPreferredUKeyProvider(): any {
// 1) 先尝试读取 UKey Wallet 自己的注入对象
const ukey = (window as any)?.$ukey?.ethereum;
if (ukey) return ukey;

// 2) 如果没拿到,再从多 Provider 列表里定位 UKey Wallet
const deviceList = (window as any)?.ethereum?.providers;
const okFromList = deviceList?.find(
(p: any) =>
p?.isUKey ||
p?.ukey ||
(typeof p?.isUKey === "function" && p.isUKey()),
);
if (okFromList) return okFromList;

// 3) 最后再退回单一注入的 Provider
return (window as any)?.ethereum;
}

export const config = createConfig({
chains: [mainnet, polygon, arbitrum, base, optimism],
connectors: [
injected({
target() {
return {
id: "ukey",
name: "UKey Wallet",
provider: selectPreferredUKeyProvider,
};
},
shimDisconnect: true,
}),
],
transports: {
[mainnet.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
[optimism.id]: http(),
},
});

接入要点

  • 优先读取 window.$ukey.ethereum,可以减少多钱包环境下选错 Provider 的概率。
  • 用户没有安装 UKey Wallet 时,连接器仍会回退到其他注入式钱包,便于你做兼容处理。
  • eth_requestAccounts 应由用户点击按钮后触发,不要在页面加载时自动请求账户。
  • 用户拒绝授权时通常会返回 4001,请在界面上提供可理解的重试提示。

应用设置

// 参考文件:main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { config } from "./ukeyConnector";
import WalletConnectionPanel from "./WalletConnectionPanel";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<WalletConnectionPanel />
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
);

连接并读取账户

// 参考文件:WalletConnectionPanel.tsx
import { useAccount, useConnect, useDisconnect } from "wagmi";

export default function WalletConnectionPanel() {
const { address, isConnected } = useAccount();
const { connect, connectors, isPending } = useConnect();
const { disconnect } = useDisconnect();

if (isConnected) {
return (
<div>
<p>已连接: {address}</p>
<button onClick={() => disconnect()}>断开连接</button>
</div>
);
}

return (
<div>
{connectors.map((c) => (
<button
key={c.uid}
disabled={isPending}
onClick={() => connect({ connector: c })}
>
使用 {c.name} 连接
</button>
))}
</div>
);
}

签名与发送交易

import { createWalletClient, custom } from "viem";

const provider =
(window as any)?.$ukey?.ethereum ||
(window as any)?.ethereum?.providers?.find(
(p: any) => p?.isUKey || p?.ukey,
) ||
(window as any)?.ethereum;

const client = createWalletClient({ transport: custom(provider) });
const [account] = await provider.request({ method: "eth_requestAccounts" });

// 个人消息签名
await provider.request({
method: "personal_sign",
requestParams: ["0x68656c6c6f", account],
});

// EIP-1559 交易参考
const txHash = await provider.request({
method: "eth_sendTransaction",
requestParams: [{ from: account, to: "0x...", value: "0x..." }],
});

事件与网络切换

  • 监听 accountsChangedchainChanged,在账户或链发生变化时同步刷新应用状态。
  • 切换或添加网络时,使用 wallet_switchEthereumChainwallet_addEthereumChain
  • 交易发送前,建议重新读取当前账户和链 ID,避免用户在钱包侧切换后仍沿用旧状态。

移动端深度链接

移动端 Web 或 WebView 可以通过 UKey Wallet Deeplink 打开钱包:

  • 自定义协议:ukey-wallet:/wc?uri={encodeURIComponent(wcUri)}
  • 通用链接:https://app.ukey.io/

故障排除

  • 4001: 用户拒绝授权或签名,请保持当前页面状态并允许用户重新点击连接。
  • 未检测到 Provider: 检查扩展是否安装、是否被浏览器禁用,以及页面是否运行在受支持环境中。
  • 多钱包冲突: 优先读取 window.$ukey.ethereum,避免误连到其他钱包。

可选:EIP-6963(多钱包发现)

如果项目已经接入 EIP-6963,可以监听钱包公告事件,在发现 UKey Wallet 时把它作为首选 Provider:

let ukeyProvider: any = null;
const providers: any[] = [];

function captureAnnouncedProvider(event: any) {
const { info, provider } = event.detail || {};
providers.push({ info, provider });
if (info?.name === "UKey Wallet" || info?.rdns?.includes?.("ukey")) {
ukeyProvider = provider;
}
}

window.addEventListener("eip6963:announceProvider", captureAnnouncedProvider);
window.dispatchEvent(new Event("eip6963:requestProvider"));

// 应用初始化时优先复用 `ukeyProvider`;拿不到时再退回 selectPreferredUKeyProvider()