选团队更容易接受的切入点
面对从 RN 转向 Electron、链范围反复的既有钱包,我先改相对独立的 Provider / RPC,单独验证后逐步接回主工程。
2025.06–2026.02 · 两个月渐进式重构
两个月,逐层重构跨端钱包的请求、状态与业务流程
面对从 RN 转向 Electron、链范围反复的既有钱包,我先改相对独立的 Provider / RPC,单独验证后逐步接回主工程。
统一权限、去重与锁定规则,接入 Send 和地址校验;用 7 份工作文档帮助产品、设计和研发共同决策。
main 管真实状态,chain-kit 管链能力;平台、状态、权限与业务职责已经拆清。
章节
两个月里,我围绕架构心智模型、现状风险、DApp 请求、Send 交互、地址校验、权限机制、前后台 Service 拆分和链能力收口,写下并向团队分享了 7 份工作文档。它们不是项目结束后的包装材料,而是当时用来讲清背景、冲突、取舍和实现路径的工具;展开可查看为什么出现、随后改变了什么,并跳到对应影片章节。
文档中的核心问题
钱包究竟是“拥有数据与行为的内存对象”,还是“数据库事实在此刻的渲染结果”?
文档中的核心问题
明文长期驻留、非原子写入、缺少迁移与一致性校验,以及无法自然扩展的账户分层。
文档中的核心问题
连接权限、重复弹窗、锁定行为与新增链,不该继续分散在 Provider、Handler 和 Service。
文档中的核心问题
让产品、设计同学意识到,从原型图、设计稿到实际落地,还差的东西。
文档中的核心问题
一个面向 Recipient 地址输入流程的小型校验工具集。
为什么写
把 Send 评审得到的交互规则写成可组合、可跨页面复用的能力。
随后发生了什么
形成 typing → settled → complete → validating → valid / invalid 状态与 validators。
文档中的核心问题
面向产品和技术团队,覆盖标准 EOA 权限流程与多维边界场景。
文档中的核心问题
一条改造把 Service 拆成 frontend / backend 以适配 Electron / Extension;另一条把散落的链能力收进 chain-kit。
为什么写
一次提交同时触及请求、进程、链与密钥,需要把真实的责任变化讲清楚。
随后发生了什么
Service 前后台分层建立宿主边界;chain-kit 独立收拢链能力,重复或虚假的 Service 被移除。
01
Git 显示仓库从 2025 年 6 月 5 日开始,到我 2026 年 1 月 8 日提交第一笔核心重构时,已经过了 217 天、累计 836 次提交。6 月先接入 Expo / React Native,9 月再加 Electron;最初的桌面构建脚本直接把 mobile/dist 复制进 Electron renderer。Solana 则从 6–8 月持续加入 Keyring、签名、RPC、交易和 Wallet Standard,后来旧 Send 的 Solana 页面又在 2 月 12 日被删除。平台和链范围没有先定清,历史实现只能层层叠加。
02
这 7 份文档覆盖 Wallet-Core 心智模型、原架构风险、DApp 请求、Send 设计评审、地址校验和权限机制。第 08 份又记录了两条彼此独立的改造:把 Service 拆成 frontend / backend 以适配 Electron / Extension,以及把散落在 crypto、vault、utils/rpc 的链能力收进 chain-kit。它们分别面向产品、设计和研发,不是等代码改完再通知团队,而是先把背景、冲突和可选路径摊开,再通过分享或评审形成共同决定。
03
Provider / DApp RPC 连接所有平台和链,但与 UI、资产和存储相比仍相对独立,可以先替换、单独验证,再逐步接回主工程。旧 Provider 用四层继承同时承担 EVM 状态、兼容方法、事件和平台通信,最后落到 globalThis.$jsBridge。我将它改成独立的 EthereumProvider / SolanaProvider:各自管理状态与事件,宿主通信只通过 Transport 接口进入,不再让 Provider 感知 RN、Electron 或 Extension 的具体 API。
04
旧请求从 service/src/dapp/request.ts 进入 getHandler(base),每次再创建 Evm / Sol Handler;权限、锁定、是否弹窗和 RPC fallback 跟着各个方法散落。新增权限要逐个方法补检查,防重复弹窗要在 handleRequest 维护 pendingApprovals,添加新链还要同时修改 Handler、映射、types 和 base 分支。我用 rpc-engine 按固定顺序执行 blacklist、日志、错误边界、权限、CAIP scope、去重、锁定和 executor;其中五项保护能力旧系统根本没有。请求最终只进入钱包 Handler、用户 Approval 或 RPC Proxy 三条明确路径。
05
旧结构把 Provider 和 JS Bridge 分在两个包:ProviderDelegate 依赖 globalThis.$jsBridge,mobile Bridge 硬编码 ReactNativeWebView.postMessage,desktop Bridge 硬编码 Electron IPC,各自还需要 buildInject 脚本拼装。新增平台要同时修改 Provider 与 Bridge,测试也必须 mock 全局宿主对象。重构后 Provider 不再感知宿主;React Native、Electron 和 Extension 分别实现 Transport Adapter。新增平台只需增加一个 Transport,测试也只需替换接口。
06
Provider / rpc-engine 在 1 月 15 日已经完成并接回 Service;真正进入 RN、Electron 与 Web 时,才发现主工程缺少承载它们的基础设施。Electron 当时尚未具备 React HMR,WebView DevTools 与 reload 会互相影响,跨宿主运行入口也不完整。我随后补齐这些通路,因为重构的完成标准不是删掉旧实现,而是新主干能在真实 App 里持续开发、调试和运行。
07
建立 Provider / RPC 主干后,我逐步接入业务:Send 与 useAddressValidation 在 1 月 28 日先合入;Permission / DApp、双进程 Service 在 2 月 10 日提交;chain-kit 在 2 月 12 日收口。相对独立的模块可以先交付、先接回真实流程,让团队逐步接受改动;UI 业务推进的同时,我继续整理权限、状态权威和链能力。
08
项目最初以 RN 为目标,Service 按“一个前端运行时里直接调用”来组织;后来加入 Electron 时,mobile web 和原有启动方式被直接复用,却没有同步定义 main / renderer 的唯一权威。renderer 是用户看到的前台页面,main 是能够访问本地数据库、网络和密钥的钱包后台;两个进程的内存并不共享。旧代码于是让两边各自 import 并启动一份 Service,看起来名字相同,实际是两只互不相通的钱包。我把完整 Service、RPC Engine、SQLite WAL 存储和 signer 放到 main,让它成为唯一真实状态;renderer 只保存脱敏镜像。用户点击通过 IPC 发给 main 执行,状态变化再以 Immer patches 同步回来,应用失败时重新拉取完整状态。
09
chain-kit 不是简单改目录:crypto、vault、utils/rpc 三个包被收成一个函数式链能力包;Transfer Service 合入 Tx;从未真正提供并发安全的 Nonce Service 被删除,改为读取 pending nonce;action 与 selector 分成写和读;Zod schema 同时承担运行时校验和类型推导。另一个独立的安全增强是 Signer 用 Uint8Array 保存自身 buffer,并在签名后清零;底层库仍短暂接收 string,这是明确保留的 JS 边界,不是“链能力归位”的证明。
010
网站连接钱包时,需要一张长期通行证,记住“哪个网站、哪条链、哪些账户可以互相看见”;但签名、转账、切链和添加资产只需要一张一次确认单。我用 Connector 保存长期关系,用 ApprovalQueue 暂存当前请求;确认、拒绝或超时后,确认单立即销毁。Connect、Sign Message、Send Transaction、Switch / Add Network、Watch Asset 和 Solana Confirmation 页面都接到这条统一流程。
011
Figma 中同一个 Recipient Picker 的列表态是 378×406,完整地址态是 378×210。问题不只是一段动画,而是输入框到底先负责搜索联系人,还是先校验地址。我先定义 typing、settled、complete、validating、valid / invalid,再把地址、域名、自转账、合约、通讯录和最近收款人拆成可组合校验器。列表固定三行、提示区固定 64px,300ms debounce 与 600ms settled 让高频抖动收敛为一次有意义的状态转换。
012
这段工作不是两个月从零造一个钱包,而是在 217 天、836 次历史提交中逐层替换。最终进入当前源码的是:独立的 Provider / Transport、可配置的 RPC pipeline、接回 Service 的跨端请求主干、DApp 连接和确认、main 权威 Service、chain-kit、Send 和地址校验。项目没有完成市场发布,但停下时已经能明确回答平台、链、状态、权限、密钥和 UI 各自归谁。
01.08
Transport、异构链 Provider 与 middleware pipeline 进入仓库。
01.14–01.15
Provider / rpc-engine 的独立实现进入主工程,宿主基建缺口随之暴露。
01.16–01.22
建立跨宿主运行入口,并启用 Electron React HMR、处理 WebView DevTools 与重载冲突。
01.28
把设计冲突翻译成输入状态、校验器、缓存与多链规则。
02.10
Connector、ApprovalQueue、DApp Confirmation 和 main/renderer bridge 成形。
02.12–02.13
三包链能力归入 chain-kit,Transfer / Nonce 收口;另为 signer buffer 增加用后清零。