Wagmi Connector 开发全指南:从零创建并上架一个钱包连接器
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
本篇指南以 Wagmi 仓库中的 creating-connectors.md 为核心脉络,系统讲解在 Wagmi 中新建、测试并向上游贡献一个 Connector 的完整流程。你将掌握createConnector工厂函数的完整契约(属性、方法、事件与配置参数)、连接器的文件组织与导出规范、测试与文档要求,以及从 changeset 到 Pull Request 的提交路径,可直接套用于packages/connectors的实际开发。
写在动手之前:先沟通,再编码
Wagmi 是一个依赖赞助与赠款维持运转的开源项目,维护第三方 Connector 需要持续投入时间与精力。因此文档在开篇就明确两点前置要求:
- 团队需要赞助 Wagmi(wevm 赞助页),这是 Connector 请求被接受的前提之一;如有疑问可邮件联系
dev@wevm.dev。 - 动手写代码之前,先创建 Connector Request 讨论(在 GitHub Discussions 的
connector-request分类下发起),说明它解决的是对 Wagmi 用户重要或通用的使用场景,并确认能得到 Wagmi 团队与 Connector 维护团队的支持,避免投入大量精力后 Pull Request 被拒绝。
同时,文档明确警告:并非所有 Connector 都会被 Wagmi 接受。判断标准包括用例的重要性、通用性、维护团队的响应能力等,具体理由贯穿本文后续的每一步要求中。
第 1 步:准备本地开发环境
先按照仓库的 贡献指南 搭建开发环境,核心步骤包括:
- 使用
git clone克隆仓库,或使用gh repo clone wevm/wagmi; - 确保安装
node@对应版本(仓库package.json的devEngines字段声明了运行时版本),并通过corepack enable启用 pnpm; - 在仓库根目录执行
pnpm install安装依赖(pnpm 会自动链接各 workspace 包并配置 git hooks); - 复制并填写
.env环境变量,其中包含链分叉 RPC 地址(VITE_MAINNET_FORK_URL、VITE_OPTIMISM_FORK_URL)与 WalletConnect 项目 ID(*_WC_PROJECT_ID)等,供开发 playground 与测试套件使用。
开发环境就绪后,运行pnpm dev:next、pnpm dev:react、pnpm dev:vue、pnpm dev:nuxt等命令即可启动./playgrounds下的对应 playground 应用,修改任意包源码(如packages/connectors)会自动热更新到 playground,方便在真实 dApp 环境中验证 Connector。
第 2 步:创建 Connector 源文件
在packages/connectors/src目录下新建一个以 Connector 命名的文件,例如新增Foo就创建foo.ts。文件名要求camelCase 且尽量简短。
从当前仓库的实际文件布局看,packages/connectors/src下已经存在的 Connector 包括baseAccount.ts、coinbaseWallet.ts、metaMask.ts、safe.ts、walletConnect.ts,以及从@wagmi/core与@wagmi/core/tempo重导出的injected、mock、tempoWallet(见 packages/connectors/src/exports/index.ts)。新文件应与之并列,命名风格保持一致。
第 3 步:用createConnector搭建骨架
所有 Connector 的根基都是@wagmi/core导出的createConnector工厂函数。文档给出的最小骨架如下:
import { createConnector } from '@wagmi/core' export type FooBarBazParameters = {} export function fooBarBaz(parameters: FooBarBazParameters = {}) { return createConnector((config) => ({})) }注意三点约定:
- 函数名与文件名一致:文件叫
fooBarBaz.ts,导出函数就叫fooBarBaz; - 导出一个工厂函数:它接收一个参数对象(
FooBarBazParameters,默认{}),返回createConnector的调用结果; createConnector接收一个回调:回调以config为参数(内含chains、emitter、providers、storage等运行时配置,详见下文「Parameters」),返回 Connector 对象本身。
从 packages/core/src/connectors/createConnector.ts 的源码可见,createConnector的实现只是把回调原样返回(一个恒等包装),它的真正价值在于通过CreateConnectorFn类型对整个返回对象做强类型约束:任何缺失的属性都会在编译期以类型错误的形式暴露出来。
写好骨架后,TypeScript 会立刻报出一个类似Type '{}' is missing the following properties...(错误码 2740)的类型错误,提示你缺少哪些属性。这正是下一步要逐个补齐的内容。
第 4 步:补齐 Properties、Methods、Events 与 Parameters
根据CreateConnectorFn的完整定义(packages/core/src/connectors/createConnector.ts),一个完整的 Connector 由四类成员构成,文档逐类列出如下。
Properties(连接器静态属性)
| 属性 | 必填 | 说明 |
|---|---|---|
icon | 否 | Connector 的可选图标 URL |
id | 是 | Connector 的唯一标识,camelCase 且尽量简短,例如fooBarBaz |
name | 是 | 人类可读的名称,例如'Foo Bar Baz' |
rdns | 否 | 可选的反向 DNS 标识,用于在启用createConfig#multiInjectedProviderDiscovery时过滤重复的 EIP-6963 注入式 Provider |
以仓库中的 metaMask.ts 为真实范例:
return createConnector<Provider, Properties>((config) => ({ id: 'metaMaskSDK', name: 'MetaMask', rdns: ['io.metamask', 'io.metamask.mobile'], type: metaMask.type, // ... }))可以看到 MetaMask Connector 的rdns是字符串数组(源码类型为string | readonly string[]),覆盖桌面与移动端两个 Provider;type属性则用模块级常量metaMask.type = 'metaMask' as const固定。type是CreateConnectorFn返回类型中的只读必填字段,用于标识连接器种类。
Methods(连接器方法)
| 方法 | 必填 | 说明 |
|---|---|---|
connect | 是 | 连接 Connector |
disconnect | 是 | 断开 Connector |
getAccounts | 是 | 返回 Connector 当前连接的账户列表 |
getChainId | 是 | 返回 Connector 当前连接的链 ID |
getProvider | 是 | 返回底层 Provider 接口,供 Connector 内部各处使用 |
isAuthorized | 是 | 返回 Connector 之前是否连接过且仍处于已授权状态 |
setup | 否 | 在 Connector 首次创建时运行的初始化逻辑 |
switchChain | 否 | 切换 Connector 的当前链 |
此外CreateConnectorFn还允许可选的getClient,用于返回 viemClient实例。connect的参数对象(CreateConnectorFn源码定义)包含chainId、isReconnecting与withCapabilities三个可选字段,返回值固定为{ accounts, chainId }结构。
Events(内部事件订阅函数)
| 事件 | 必填 | 说明 |
|---|---|---|
onAccountsChanged | 是 | 订阅账户变更 |
onChainChanged | 是 | 订阅链变更 |
onConnect | 是 | 订阅连接事件 |
onDisconnect | 是 | 订阅断开事件 |
onMessage | 否 | 订阅 Provider 消息 |
这些回调的职责是把底层 Provider 的事件转发给 Wagmi 的config.emitter,从而同步 Connector 状态与Config。以 metaMask.ts 为例:
async onAccountsChanged(accounts) { config.emitter.emit('change', { accounts: accounts.map((x) => getAddress(x)), }) }, onChainChanged(chain) { const chainId = Number(chain) config.emitter.emit('change', { chainId }) }, async onConnect(connectInfo) { const accounts = await this.getAccounts() if (accounts.length === 0) return const chainId = Number(connectInfo.chainId) config.emitter.emit('connect', { accounts, chainId }) }, async onDisconnect(error) { // 处理 MetaMask 的 code: 1013 特殊错误:等待重连而不是直接断开 if (error && (error as RpcError<1013>).code === 1013) { const provider = await this.getProvider() if (provider && Boolean((await this.getAccounts()).length)) return } config.emitter.emit('disconnect') },Parameters(createConnector回调注入的配置)
回调收到的config对象(CreateConnectorFn的参数类型,见 createConnector.ts)包含以下字段:
chains:用户配置的链列表,类型为readonly [Chain, ...Chain[]](至少一条链)。switchChain中常用它查找目标链并校验是否已配置;emitter:事件发射器,用于把 Connector 状态同步到 WagmiConfig。可用事件共五类(ConnectorEventMap定义于同一源码文件):change:连接的账户或链发生变化({ accounts?, chainId? });connect:Connector 完成连接({ accounts, chainId });disconnect:Connector 断开;error:Connector 收到错误({ error });message:Connector 收到消息({ type, data? });
providers:与 Connector 的rdns匹配的已发现 EIP-6963 Provider 列表;若multiInjectedProviderDiscovery被禁用或没有匹配的 Provider,则为空数组;storage:用户可选配置的存储,默认是对localStorage的封装。safe.ts的shimDisconnect功能正是利用config.storage?.setItem/getItem/removeItem('safe.disconnected')记录断开标记(见 safe.ts);transports(源码中还有此可选字段):链 ID 到Transport的映射。
文档同时给出两条贯穿实现始终的 tip:
- 第三方 SDK 依赖纪律:若 Connector 使用第三方 SDK,它应依赖尽量少(控制 bundle 体积、降低供应链攻击面)、使用尽可能宽松的开源许可(理想为 MIT),且其
package.json应声明"sideEffects": false,以最大化 tree-shaking 支持。 - 地址一律校验和(checksum):Connector 返回或发出的所有地址值都必须使用 Viem 的
getAddress工具做校验和格式化。仓库中几乎所有 Connector 都遵循此约定,例如safe.ts中(await provider.request({ method: 'eth_accounts' })).map(getAddress)。
第 5 步:导出 Connector
在packages/connectors/src/exports/index.ts中按字母顺序导出新 Connector:
export { fooBarBaz } from './fooBarBaz.js'从 index.ts 的实际内容可以看到,现有导出严格按字母序排列(injected、mock、tempoWallet、baseAccount、coinbaseWallet、metaMask、safe、walletConnect以及version),新增的fooBarBaz应插入到相应位置。
第 6 步:在 playground 中试跑并编写测试
开发过程中可以借助 dev playgrounds 在真实 dApp 环境里验证 Connector 行为(pnpm dev:next/pnpm dev:react/pnpm dev:vue等)。文档对测试的要求是:
- 理想情况:在
connectorName.test.ts中编写真实单测; - 最低要求:若单测难以编写,至少创建测试文件并包含"指令式测试"(instruction tests),覆盖以下三类场景:
- 如何连接 Connector;
- 如何断开 Connector;
- 如何切换 Connector 的当前链(如适用)。
测试文件必须写清楚验证所需的一切信息,例如需要安装的软件(浏览器扩展、移动 App 等)、需要交互或部署的智能合约等。
从仓库现状看,packages/connectors/src下的每个 Connector 都配有同名测试文件(metaMask.test.ts、safe.test.ts、walletConnect.test.ts等)。另外还要更新导出清单测试 packages/connectors/src/exports/index.test.ts——该文件用 Vitest 的toMatchInlineSnapshot断言导出键的完整集合,新增 Connector 后必须同步更新快照,可以手动改,也可以直接运行:
pnpm test:update packages/connectors/src/exports/index.test.ts第 7 步:把团队成员加入 CODEOWNERS
Connector 必须被及时更新、持续维护,生产环境用户才能放心依赖。Wagmi 核心团队会尽可能协助 Connector 跟上 Wagmi 的破坏性变更,但依赖处理与 issue/discussion 响应是你所在团队的职责——若问题长期无人处理,该 Connector 可能被从 Wagmi 中移除。
为此,需在 .github/CODEOWNERS 中为 Connector 至少登记一名团队成员,格式如下:
/packages/connectors/src/fooBarBaz @tmm @jxom仓库现有的 CODEOWNERS 就为每个第三方 Connector 登记了对应生态团队,例如:
/packages/connectors/src/metaMask @wenfix @ffmcgee725 @jiexi @adonesky1 @chakra-guy /packages/connectors/src/safe @DaniSomoza @dasanra @mikhailxyz @yagopv /packages/connectors/src/walletConnect @ganchoradkov @glitch-txs @ignaciosantise @tomiir登记后,涉及该 Connector 的 Pull Request、issue 等都会自动通知到对应维护者。
第 8 步:编写 Connector 文档
Connector 需要配套文档。参考贡献指南的「Writing documentation」一节 启动文档站点(运行pnpm docs:dev),并添加所需页面。Wagmi 文档站位于./site,使用 VitePress 构建;site/shared/connectors/目录下已有baseAccount.md、coinbaseWallet.md、injected.md、metaMask.md、safe.md、tempoWallet.md、walletConnect.md等页面可作为新 Connector 文档的参照模板,注意保持文档简洁、使用平实语言。
第 9 步:创建 changeset
功能与测试就绪后,运行以下命令创建 changeset:
pnpm changeset按文档要求,该 changeset 应是对@wagmi/connectors仓库的一个patch版本变更,描述格式为Added [ConnectorName],例如Added Foo Bar Baz。changeset 决定了发布时的版本号与 CHANGELOG 文案(变更描述用过去时态动词,如Added/Fixed,这是仓库贡献指南明确约定的命名规范)。
第 10 步:提交 Pull Request
一切就绪后,按贡献指南的 Pull Request 提交规范创建 Pull Request,使用祈使语气命名(如Add something)。合并后,Connector 会进入 Wagmi 的未来版本发布。提交后 GitHub 会自动执行 lint、构建与测试,若出现 ❌ 大多说明代码有 bug,请通过 CI 日志排查。
真实 Connector 源码速览:createConnector的三种落地形态
为了让上文的抽象契约更具体,这里快速对照仓库中三个 Connector 的典型实现模式(都可作为你编写新 Connector 的参考模板):
- metaMask.ts:基于
@metamask/connect-evmSDK 的完整实现。展示了"SDK 实例懒加载 + 动态 import"模式(getInstance()内部使用await import('@metamask/connect-evm')并按需创建客户端)、错误归一化(把UserRejectedRequestError、ResourceUnavailableRpcError映射为 viem 的错误类型)、switchChain中利用addEthereumChainParameter与config.chains拼装wallet_switchEthereumChain所需的链配置,以及isAuthorized中使用withRetry/withTimeout处理移动端 Provider 在页面加载时的 JSON-RPC 响应延迟。 - safe.ts:展示了
storageItem泛型的使用——createConnector<Provider, Properties, StorageItem>中的StorageItem = { 'safe.disconnected': true }为存储键提供了类型安全;同时演示了shimDisconnect通过config.storage记录断开标记、switchChain可省略(Safe 智能合约钱包仅存在于单链)、以及通过window.parent !== window判断 iframe 环境(Safe App 必须运行在 iframe 中)的细节。 - walletConnect.ts:基于
@walletconnect/ethereum-provider,其参数类型通过Omit<EthereumProviderOptions, ...>精确剪裁 SDK 暴露面,并额外定义了isNewChainsStale这样的 Wagmi 专属语义选项,展示 Connector 如何在 SDK 之上封装 dApp 友好的配置接口。
这三个文件连同各自的*.test.ts测试与site/shared/connectors/下的文档页,构成了一份"可运行的 Connector 参考实现集"。按照本文的十步流程,结合这些范例,你就能完整走通从想法到上游合入的 Connector 开发全链路。
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考