← 返回 MegaWallet 工作文档

MEGAWALLET · WORKING DOCUMENT 08

chain-kit 与 Service 架构

原始文件 · my-history/8.chain-kit-and-service-architecture.md

涉及 commit: 7a8f537d (feat: Add Permission&Dapp) + e25fe09d (refactor: chain-kit) 总计 431 文件变动,+15,413 / -10,1282 行


一、chain-kit 重构(核心变动)

目的

将原来分散在三个包(cryptovaultutils/rpc)中的区块链操作统一收敛到 packages/chain-kit 一个包,从 class-based OOP 转向 function-based 模块化设计。同时将 service 层中不涉及状态管理的链操作(如 Service/Nonce)也下沉到 chain-kit,service 只负责业务编排。

前后对比

维度 旧架构 新架构
包结构 crypto(签名) + vault(keyring管理) + utils/rpc(RPC调用) 统一 chain-kit
范式 class 继承链:BaseCrypto → EvmCryptoBaseVault → EvmVault 纯函数模块:evm.signTransaction()sol.broadcastTx()
签名接口 vault.signTransaction() 需先实例化 Vault createSigner(params) 返回 SignerInstance 对象
RPC 接口 new EvmRpc(url) 类实例 evm.getBalance(config, addr) 纯函数 + 内部 client 缓存
Keyring 独立 HDKeyring/PKKeyring/WatchKeyring 集成到 SignerInstance.init(secret) 内部
私钥生命周期 无管理,私钥驻留内存直到 GC 签名后自动销毁,引用计数支持批量场景
Tree-shaking 差(class 整体引入) 好(按函数引入)

新 chain-kit 模块结构

packages/chain-kit/src/
├── signer.ts              # SignerInstance 抽象 + createSigner 工厂 + 私钥自动销毁
├── chains/
│   ├── evm/
│   │   ├── signer.ts      # EVM 签名(viem)
│   │   ├── rpc.ts         # EVM RPC(balance/gas/broadcast/ENS)
│   │   ├── tx.ts          # 构建 unsigned tx
│   │   ├── fee.ts         # gas 估算
│   │   ├── serialize.ts   # tx 序列化
│   │   └── contract/      # ERC20/721/1155 ABI helpers
│   └── sol/
│       ├── signer.ts      # Solana 签名(OKXWeb3 SDK)
│       ├── rpc.ts         # Solana RPC
│       ├── tx.ts          # 构建 transfer tx
│       └── serialize.ts   # tx 序列化
└── index.ts               # 统一导出

删除的包/模块

SignerInstance 私钥自动销毁机制

旧架构中 Vault 实例的私钥在签名后无清理,明文驻留 JS 堆内存直到 GC 回收(时机不确定,且不保证内存清零)。新架构在 SignerInstance 内部实现了自动销毁:

核心设计:Uint8Array 安全存储 + 引用计数 + sign 后自动 destroy

各场景覆盖:

场景 路径 销毁方式
DApp 签名(EVM) eip155.ts → getVaultsignMessage/signTransaction sign 方法 finally 自动销毁
DApp 签名(Solana 单笔) solana.ts → getVaultsignTransaction/signMessage sign 方法 finally 自动销毁
DApp 批量签名(Solana) solana.ts → getVaultacquire → N × signTransactionrelease release 归零后销毁
Trade/Send 转账 Tx/action.ts → getVaultsignTransaction sign 方法 finally 自动销毁
导入/派生钱包 Wallet/action.ts → importMnemonic/deriveHD/importPrivateKey getFullInfoFromVault 后手动 destroy()
导出私钥/助记词到页面 exportMnemonic/exportPrivateKey 直接解密返回字符串 与 signer 无关,由组件 state 管理

优劣势

优势:

劣势:

其他钱包参考

chain-kit 类型规范

遵循「推导 > 定义」原则,chain-kit 内部类型约定:


二、Service 双进程架构(Frontend/Backend Bridge)

目的

为 Electron 桌面端实现 main process(后端)和 renderer process(前端)的进程隔离。Service 全量运行在 main process,renderer 通过 IPC bridge 访问。

前后对比

维度 旧架构 新架构
进程模型 单进程,Service 直接在 renderer 运行 双进程:main process 持有 Service,renderer 持有 ProxyService
IPC packages/channel 通用 event-bus 专用 transport 层(4 个 IPC channel)
状态同步 无(同进程直接访问) Immer patch 增量同步 + 全量 fallback
安全性 renderer 可直接访问 vault/密钥 renderer 只能通过 action 调用,敏感字段被过滤

架构图

┌─ Main Process ──────────────────────────────────┐
│  Service (13 stores + effects + RPC Engine)     │
│  ↕ onPatch callback                             │
│  ElectronBackendChannel                         │
│    - service:action   (req/res)                 │
│    - service:sync     (req/res)                 │
│    - service:patches  (broadcast)               │
│    - service:event    (broadcast)               │
└─────────────────────────────────────────────────┘
                    ↕ Electron IPC
┌─ Renderer Process ──────────────────────────────┐
│  ElectronFrontendChannel                        │
│  ↕                                              │
│  ProxyService (13 proxy stores)                 │
│    - 只读 state + async action wrappers         │
│    - patch 增量更新 state                        │
│    - 失败时 full sync fallback                   │
└─────────────────────────────────────────────────┘

关键实现

删除的包

优劣势

优势:

劣势:

其他钱包参考


三、DApp 弹框 + Approval/Connector 机制

目的

重建 DApp 连接和审批系统:用 Connector(持久化连接管理)+ ApprovalQueue(临时审批队列)替代旧的 Session + Approval。同时新建完整的 DApp Confirmation UI。

前后对比

维度 旧架构 新架构
连接管理 Session service(per-origin, per-namespace) Connector service(per-origin, per-CAIP-2 chain)
审批模型 Approval service(持久化,status-based) ApprovalQueue(临时,Promise-based)
权限检查 hasPermission() 查 Session.walletMap isConnected() 查 Connector.connections
链标识 namespace(evm/sol) CAIP-2(eip155:1, solana:mainnet)
UI ConfirmationProvider + 空壳 DApp Popup DAppManager + BlurModal + 完整 Confirmation 组件树
审批持久化 是(Approval 存储完整 request/response) 否(ApprovalQueue 仅内存,Promise resolve 后消失)

新 Connector 模型

Connection {
  origin: string           // DApp 域名
  caip2: Caip2ChainId      // "eip155:1" | "solana:mainnet"
  accounts: AccountRef[]   // 授权的钱包/账户
  siteMetaData: { name, icon }
  grantedAt / updatedAt / lastSelectedAt / expiresAt
}

核心 action:connect() / disconnect() / addAccount() / removeAccount() / sweepInvalid() / sweepExpired()

新 ApprovalQueue 模型

PendingApproval {
  id: string
  origin: string
  caip2: Caip2ChainId
  method: string           // "eth_sendTransaction" 等
  data: unknown
  createdAt: number
}

核心 action:requestApproval() → 返回 Promise / approve(id, result) / reject(id, error) / rejectAll()

端到端流程(以 eth_sendTransaction 为例)

DApp 调用 provider.send('eth_sendTransaction', [...])
  → RPC Engine 检查 isConnected(origin, caip2, address)
  → approval handler 调用 approvalQueue.requestApproval({...})
  → DAppManager 检测到 pending approval → 弹出 BlurModal
  → 用户确认 → approvalQueue.approve(id, result)
  → Promise resolve → handler 签名 + 广播 → 返回 txHash

DApp Confirmation UI 结构

packages/view/src/DApp/
├── index.tsx                    # DAppManager(监听 approvalQueue)
└── Confirmation/
    ├── index.tsx                # 按 method 路由
    ├── Layout.tsx               # 通用确认布局(app info + 按钮)
    ├── Connect/                 # eth_requestAccounts
    ├── SwitchNetwork/           # wallet_switchEthereumChain
    ├── AddNetwork/              # wallet_addEthereumChain
    ├── WatchAsset/              # wallet_watchAsset
    └── chains/
        ├── Evm/
        │   ├── SignMessage/     # personal_sign, eth_signTypedData
        │   └── SignTransaction/ # eth_sendTransaction
        └── Sol/                 # solana_signTransaction 等

删除的模块

优劣势

优势:

劣势:

其他钱包参考


四、Service 内部 action/selector 职责分离

目的

将所有不涉及 state 变更的逻辑从 action 移到 selector,确立 action = 写、selector = 读 的清晰边界。

前后对比

维度 旧架构 新架构
action 混合:state 变更 + 查询 + 派生计算 纯写入:set*add*remove*update*fetch*
selector 简单 getter 承担所有读操作:lookup、filter、search、computed、count

具体迁移示例

例外

有副作用的函数(如 exportMnemonic()getVault() 涉及解密)仍留在 action。

意义


五、类型系统:推导 > 定义

目的

用 Zod schema 作为类型的 single source of truth,通过 z.infer<typeof Schema> 推导类型,逐步淘汰 packages/types/ 中手写的 interface。

前后对比

维度 旧架构 新架构
类型来源 packages/types/ 手写 interface service schema z.infer<> 推导
交易类型 6 个独立 interface(TransferNative/Token/NFT × EVM/Sol) 2 个合并 interface(TransferTx × EVM/Sol),optional token/NFT 字段
验证 类型和运行时验证分离 Zod schema 同时提供类型 + 运行时验证

具体变化

Tx 类型合并:

旧:IEvmTransferNativeTx / IEvmTransferTokenTx / IEvmTransferNFTTx / IEvmStakingTx
新:IEvmTransferTx { ..., token?: {...}, NFT?: {...} }

Service schema 推导模式:

// Connector/schema.ts
export const ConnectionSchema = z.object({ origin: z.string(), caip2: ..., accounts: ... })
export type Connection = z.infer<typeof ConnectionSchema>

// Tx/schema.ts
export const TxSchema = z.object({ id: z.string(), type: z.enum([...]), status: z.enum([...]), ... })
export type Tx = z.infer<typeof TxSchema>

已删除的类型文件

仍保留在 types/ 的(后续会逐步迁移)

address、api、auth、currency、fee、history、NFT、network、token、wallet 等 — 等各自 service schema 完善后迁入


六、其他重要变动

6.1 Send 流程合并

packages/view/src/Trade/SendNew/ 改为 packages/view/src/Trade/Send/

6.2 Transfer Service 合并进 Tx

packages/service/src/Transfer/ 整个删除,逻辑合并到 Tx/action.ts

6.3 Nonce Service 删除

变动: 整个 packages/service/src/Nonce/ 目录删除,nonce 获取逻辑内联到 Tx/action.ts,直接调用 evm.getNonce()

原因: 旧实现在广播成功后本地 incrementNonce(),但 fetchNonce() 每次都走 RPC 从链上拿,本地值从未被读取 — incrementNonce 是死代码,给人一种“并发安全”的错觉但实际不起作用。

修复方式:

  1. rpc.tsgetNonceblockTag: 'pending',确保拿到包含 mempool 中 pending 交易的 nonce
  2. 删除广播后的 incrementNonce 调用
  3. 删除整个 Nonce Service(schema/action/selectors/effects),从 core.tsfrontend.tsbackend.ts 中移除所有引用
  4. Tx/action.tsfetchNonce 改为内联函数,直接调 evm.getNonce

为什么不需要本地 nonce:

其他钱包对比:

钱包 Nonce 策略 本地状态
MetaMask @metamask/nonce-tracker + per-(chainId, address) mutex 锁,考虑 pending tx 列表 有,但通过 mutex 保证一致性
Rainbow 同时拿 pending + latest 两个 count,对比本地状态,处理 private mempool 超时 有,用于 private mempool 场景
Mega Wallet getTransactionCount('pending') 每次从链上拿,无本地状态

MetaMask/Rainbow 维护本地 nonce 是因为它们面对的是 12s+ 出块的标准 EVM 链 + private mempool 场景。MegaETH 10ms 出块 + 无 private mempool,链上 nonce 始终是最新的,本地维护纯属多余。

6.3 DApp 多标签浏览

新增 packages/store/src/apps.ts(Zustand store)管理多 DApp 标签页:

6.4 新增 Hooks


七、影响范围总览

变动级别 说明
chain-kit(新) 🔴 全新 替代 crypto + vault + utils/rpc
crypto 🔴 删除 被 chain-kit 替代
vault 🔴 删除 被 chain-kit/signer 替代
channel 🔴 删除 被 service/internal/transport 替代
service 🔴 大改 新增 internal/bridge、Connector、ApprovalQueue;删除 Session、Approval、Transfer、Nonce、Notifaction
rpc-engine 🟡 中改 适配新 Connector 权限模型,删除 sessionScopes
view 🟡 中改 新增 DApp/Confirmation 完整 UI;Send 流程合并;删除旧 DApp 空壳
hooks 🟡 中改 useService hooks 适配双进程;新增 useCurrent、useEstimateEvmFee
router 🟢 小改 删除 ConfirmationProvider
store 🟢 小改 新增 apps store
types 🟢 小改 tx 类型重构,删除 dapp/message 类型
utils 🟢 小改 删除 rpc/、event.ts、global.ts 部分导出
provider 🟢 小改 适配 chain-kit import
components 🟢 小改 BlurModal、NetworkList 小调整
ui 🟢 小改 WebView 组件适配
desktop 🟡 中改 main.ts 适配双进程 Service 初始化
constants 🟢 小改 删除 event-bus 常量