返回经历

Conflux · DApp Developer Infrastructure

use-wallet× dapp-omnibus

先把钱包、链和 UI 的责任拆开,再让每个 DApp 只组装自己需要的部分

为需要同时连接 Conflux Core 与 EVM 钱包、又有自定义连接界面的 DApp,我自研了 use-wallet:保留链协议差异,只提供状态与命令,不绑定界面。dapp-omnibus 延续这个方向,将多钱包选择、授权与交易解码沉淀成公共能力。下面先讲我为何这样设计,再展开这些能力与项目的后续演进。

2022
use-wallet 初始提交年份
2 链族
Conflux Core / Ethereum RPC
3 模型
注入钱包 / WalletConnect / EIP-6963
5 包
当前 dapp-omnibus 发布边界

9 章 · 2 分 33 秒

01

2022 年自己做,不是为了重复造一个 ConnectButton

use-wallet 在 2022 年初就已有实现,old 分支保留了 1 月的钱包代码与 2 月的发布记录;现主线初始化记录的作者日期为 5 月 13 日。我的自研动机是同时支持 Conflux Core 与 EVM,保持轻量,并让每个 DApp 自由设计连接界面。以 RainbowKit 0.1.0 作方案对照:其官方用法导入 base styles,以 wagmi + ethers 配置 provider,嵌套 WagmiProvider / RainbowKitProvider,再使用 ConnectButton,定制入口主要是显示项 props 与主题变量。这里还需要 cfx_*、Base32 地址、latest_state 和 CIP-23,因此 use-wallet 只封装 window provider 和连接状态,不提供固定 UI;现主线初始 README 记录的体积目标是源码 gzip 约 3.7 KB,包含 decimal.js 时约 20 KB。

  • 不是否定 wagmi / RainbowKit,而是当时的问题边界不同
  • 链差异必须保留:Conflux Core 不能只当作另一个 EVM chainId
  • UI 所有权必须留在产品:hooks 与命令先于 ConnectButton

02

use-wallet 真正困难的是连接生命周期,而不是四个 hooks

Provider 可能尚未注入、被其他钱包伪装、检测超时、返回空账户,或者在余额 RPC 卡住时只拿到 chainId 与 accounts。Emitter 把这些情况收进 in-detecting、not-installed、not-active、in-activating、active 五态;检测完成后同时绑定 accountsChanged / chainChanged,并用 batchGetInfo 并行读取链与账户。余额有 1.25 秒保护线:即使节点余额请求失灵,也先提交账户和链,避免整个连接界面被一个 RPC 拖死。状态只在值真正变化时 emit,active 也由是否存在账户推导。

03

异构链不是把 API 名字统一,而是把差异关进适配器

ConfluxRPCMethod 与 EthereumRPCMethod 对上层暴露 connect、sendTransaction、personalSign、typedSign、addChain、switchChain、watchAsset 等同名动作,但内部保留真实协议:cfx_requestAccounts 对 eth_requestAccounts,cfx_getBalance(..., latest_state) 对 eth_getBalance(..., latest),CIP23Domain 对 EIP712Domain,wallet_addConfluxChain 对 wallet_addEthereumChain。Fluent 还能请求跨网络账户权限,并在 Core / EVM 两套地址之间切换。收益是产品代码拥有一致心智模型,而不是以牺牲链语义换取表面统一。

04

细粒度 hooks 同时解决性能与 UI 所有权

useStatus、useAccount、useChainId、useBalance 分别订阅独立 slice;只显示账户的 Header 不会因为余额轮询而重渲染。useBalance 还维护模块级引用计数:第一个订阅者挂载时启动余额 tracker,最后一个卸载时停止,所以持续 RPC 成本与真实 UI 需求绑定。React 用 Zustand,Vue 用 shallowReactive / computed,但二者都只是同一 Emitter 的薄适配;连接流程和链协议不需要复制。因为库不输出连接页面,项目可以用任意设计系统、Modal、账户卡片和响应式布局。

05

AccountManage 解决了“每个钱包都很好用,但一起用就失控”

单个 use-wallet store 能描述一个钱包;真实 DApp 却要同时列出 Fluent Core、Fluent EVM、MetaMask、OKX、TokenPocket、WalletConnect,以及浏览器动态宣布的 EIP-6963 钱包。AccountManage 为每个 WalletProvider 建立独立 Zustand store,再由 currentWalletName 选择哪一份 account、accounts、chainId、status 与 balance 投影到全局 store。切换钱包时旧订阅会先释放,新钱包用 fireImmediately 接管;当前钱包名和可持久状态会保存,balance 则明确不持久化。useRegisteredWallets 还能按 rdns / 名称去重并排序。影片用一个连接界面示意展开这种分工:桌面将钱包排成列表,移动端放进底部面板,两种布局读取同一注册表、调用同一个 connect(name),并显示同一当前钱包。产品不用为了换布局再实现一次连接逻辑。

06

WalletConnect 最难的部分,是让 CIP 会话穿过 EIP 通道

WalletConnect 插件先把 SignClient、二维码、approval、session_update 与 session_delete 适配成 WalletProvider 生命周期。更特殊的是 Conflux Core:兼容层把 cip155 chain 编码进 eip155:201029… 占位命名空间,把 Base32 地址转为 hex,把 cfx_ 方法在运输边界映射为 eth_,收到会话数据后再拆回 CIP。这样 WalletConnect 仍使用它理解的 eip155 会话格式,上层 AccountManage 却继续看到同一种 account / chainId / status。它是一层明确的协议兼容封套,不是假装两条链完全相同。

07

Approve 管理把最容易散落的交易前置逻辑收成状态机

涉及 ERC20 授权的 Swap、Stake、Bridge 常常重复 allowance 读取、输入变化防抖、按钮禁用、授权交易、回执等待和重新读取。useApproveStatus 把界面收敛为 checking-approve、need-approve、approving、approved 四态;useAuthERC20Token 在真正写入前再次 refresh,若已有 allowance 则调用 increaseAllowance 补差额,否则 approve 差额,等待 receipt 后再刷新。已有额度时的路径要求代币合约支持 increaseAllowance,并不适用于任意 ERC20。优势不是少写一个按钮,而是让适配代币的多个业务流程共享同一套“先读事实、再决定写入、最后确认新事实”的闭环。

08

Decode Action 把十六进制交易还原成可以设计的产品语义

以影片中的示例交易为例:transfer(to, 25000000),结合示例代币 DEMO 的 6 位精度,可以表达为“向 0x7a00…0042 转出 25 DEMO”。钱包活动流可以显示一句摘要,浏览器则分列接收地址和数量;这是一组解码与展示示意,不是真实交易录像。背后的 decodeData 先用 method selector 白名单与 ERC20 / 721 / 1155 ABI 解析 calldata,再结合 token metadata 判断 transferFrom 究竟是 ERC20 还是 NFT;如果有 receipt events,则优先采用事件事实,并对 Mint / Burn、TransferSingle / TransferBatch 做过滤与聚合。最终它不直接输出固定组件,而是按 ERC20_Transfer、ERC721_Approved、ERC1155_BatchMint 等 action type 调用 customUI renderer。钱包活动流、交易确认页和区块浏览器因此可以共享解码规则,却用完全不同的视觉表达。代价也明确:它是白名单解码器,未知协议必须显式扩展,不能把“成功解析”当作任意 calldata 的完整语义证明。

09

Omnibus 的价值来自边界,而不是把所有东西塞进一个包

当前 monorepo 发布 @cfx-kit/utils、dapp-utils、react-utils、ui-components、dapp-components 五层。dapp-utils 包含地址验证与转换、typed contract helpers、JSON-RPC batch / Multicall、NFT metadata 的 server→contract→IPFS fallback、确定性账户头像和 Decode Action;react-utils 承担 AccountManage、Approve 与 React helpers;ui-components 用 Zag state machine 暴露 trigger、content props、marker 与 render props,让项目接管 DOM 和样式。证据边界同样重要:dapp-components 中除 AccountAvatar 外仍有明显占位实现,当前源码也缺少覆盖 AccountManage 的测试,所以本页把它描述为持续演进的基础设施,而不是完整成熟的设计系统。

项目演进(含后续维护)

2022.01–05

早期实现与现主线初始化

old 分支在 1 月已有钱包实现,2 月出现 npm first release 记录;现主线根提交的作者日期是 5 月 13 日,已拆出 Conflux / Ethereum RPC、React / Vue 适配与细粒度 hooks。

2022.07

余额能力向交易场景延伸

加入最大可用余额 tracker,把 Gas 估算和余额变化从页面组件移到可复用逻辑。

2023.02–06

钱包矩阵继续扩展

Ethereum 侧加入 TokenPocket、Halo 等适配器,保持上层 hooks 和命令不变。

2023.07.11–12

dapp-omnibus 与 AccountManage

monorepo 初始化后,AccountManage 很快从 Recoil 改为 Zustand:每个钱包一份 store,当前钱包是一份可切换投影。

2023.11

Approve 进入基础层

allowance 读取、四态 UI、增量授权、回执等待与刷新被收成一条可复用流程。

2024.01

WalletConnect 进入同一账户模型

会话、二维码、账户与链变化被适配成 WalletProvider;CIP / EIP 命名空间通过兼容层互转。

2024.07–09

AccountManage 成为可枚举注册表

补齐状态、余额、注册钱包列表和排序,让任意自定义连接面板只消费数据与命令。

2025.05–06

EIP-6963 动态发现

同一浏览器里多个注入钱包可以动态注册,并按 rdns / 名称去重与排序。

2025.11–2026.06

项目后续维护

仓库继续完善 addChain、IPFS gateway、地址与错误处理,并以 5 个发布包维护。

证据边界

本页依据本地 use-wallet 与 dapp-omnibus 源码、CodeGraph 调用链、两个仓库的 Git 历史、dapp-omnibus changelog,以及 RainbowKit 0.1.0 的历史 README / package manifest 整理。dapp-omnibus 本地 checkout 是只含一个 grafted 提交的 shallow clone,因此历史时间线由 GitHub 公共提交记录补齐。页面只主张源码可证明的架构、API 与演进,不主张外部采用量、业务收入或未测试的运行稳定性。