面向产品和技术团队。覆盖 EIP-2255 权限提案生态、行业落地现状、标准 EOA 权限流程,以及一张多维场景对比矩阵。
第一部分:权限提案全景 — 标准在哪,行业在哪
1. 标准 EOA 权限相关提案:从哪来,为什么会有
按时间线梳理,这些提案的出现是一个逐步“补洞”的过程:
2015 — EIP-191:签名消息标准
最早期,链上只有裸交易签名。191 定义了 personal_sign 的消息前缀格式(\x19Ethereum Signed Message:\n),让签名消息和交易签名区分开,防止 DApp 骗用户签一条消息实际上是一笔交易。
2017 — EIP-1193:Provider 接口标准
DApp 和钱包之间需要一个通信协议。1193 定义了 window.ethereum 这个 provider 对象的标准接口 — request()、事件机制(accountsChanged、chainChanged)等。这是整个 DApp ↔ 钱包交互的基础管道,后面所有提案都跑在这个协议上。
2018 — EIP-712:结构化数据签名 191 只能签纯文本,用户看到的是一串 hex。712 让 DApp 可以提交结构化的类型化数据,钱包以人类可读的方式展示字段和值。这对 DeFi 场景(permit 签名、订单签名)至关重要。
2018 — EIP-1102:显式账户请求
在 1102 之前,DApp 可以直接读 eth_accounts 拿到用户地址,无需任何授权。这是一个隐私问题 — 用户打开任何 DApp 页面,地址就暴露了。1102 要求 DApp 必须调 eth_requestAccounts 显式请求,钱包弹窗让用户确认。这是第一个“权限”概念的引入。
2019 — EIP-2255:通用权限框架
MetaMask 团队把 1102 的思路泛化。在 2255 的视角下,eth_requestAccounts 等同于 wallet_requestPermissions({ eth_accounts: {} }) — 请求 eth_accounts 这个 restricted method 的权限。2255 定义了:
wallet_requestPermissions— 请求权限wallet_getPermissions— 查询权限- caveat 机制 — 对权限施加约束条件
设计愿景很大:用户可以设定交易限额、限制交互合约、设置自动登出计时器。但实际上 caveat 只定义了 { type: string, value: any } 的开放接口,没有指定任何标准 caveat 类型,规范中的 filterResponse、requiredMethods 都只是举例。这个“完全开放”的设计本意是前向可扩展,结果变成了谁也不去扩展它。
2021 — EIP-3085 / EIP-3326:网络管理
多链生态爆发后,DApp 需要引导用户切换到正确的链。3326 定义了 wallet_switchEthereumChain,3085 定义了 wallet_addEthereumChain(添加用户钱包里没有的链)。这些是非受限方法,但钱包仍弹窗确认 — 添加恶意 RPC 可能导致钓鱼。
2023 — EIP-6963:多钱包发现
1193 定义的 window.ethereum 是全局单例,多钱包安装时互相覆盖(后装的赢)。6963 让每个钱包通过事件广播自己的存在,DApp 监听后展示选择列表。解决了“我装了 MetaMask 和 Rabby,但 DApp 只看到一个”的问题。
它们之间的关系:
EIP-1193(通信管道)
└─ EIP-1102(账户请求)→ EIP-2255(通用权限框架 + caveat)
└─ EIP-3085 / 3326(网络管理)
└─ EIP-191 → EIP-712(签名标准演进)
EIP-6963(发现层,解决 1193 的单例问题)
1193 是地基,所有交互跑在上面。1102 → 2255 是权限层的演进。3085/3326 补了多链场景。191 → 712 是签名能力的演进。6963 修了 1193 的多钱包缺陷。
3. 行业现状:MetaMask 是天花板,其他钱包连这层都没做
| 钱包 | EIP-2255 权限系统 | 实际 caveat 使用 | 差异化方向 |
|---|---|---|---|
| MetaMask | 完整实现(PermissionController + restricted/unrestricted 方法分类 + MIP-2 撤销) | 仅 restrictReturnedAccounts(过滤返回账户) |
权限架构、Snaps 插件、Delegation Toolkit |
| Rabby | 未实现,兼容 MetaMask 接口 | 无 | 交易模拟/预览、合约风险分析、自动链切换 |
| OKX Wallet | 未实现,兼容 MetaMask 接口 | 无 | 多链覆盖、一站式交易所+钱包、内置 DEX/NFT |
| Rainbow | 未实现,兼容 MetaMask 接口 | 无 | NFT 展示、极简 UX、链上 Approval 管理界面 |
| Coinbase Wallet | 未实现,兼容 MetaMask 接口 | 无 | 法币入金、与 Coinbase 交易所无缝衔接 |
TokenPocket 文档原话:“eth_accounts 目前是唯一的权限,这个方法就是你现在需要的全部。” — 基本代表了除 MetaMask 外整个行业的态度。
4. 为什么大家都没做?
- DApp 侧不需要。 没有 DApp 会调
wallet_requestPermissions请求复杂权限,也没有 DApp 读wallet_getPermissions检查 caveats。所有 DApp 就做一件事:eth_requestAccounts拿地址,然后逐次调签名。 - EOA 的本质限制。 RPC 层的 caveat 只能过滤返回值或控制方法调用权,无法在链上强制执行交易限制。用户一旦点了签名确认,链上执行不受钱包管控。真正的约束必须在链上执行,而 EOA 没有可编程性。
- 竞争差异化方向不在这里。 各钱包把精力放在了交易安全(模拟/风险检测)、多链 UX、一站式功能上,而不是权限 caveats。
- MetaMask 做权限系统也是为未来铺路。 PermissionController 真正发挥价值的地方是 Snaps(插件需要严格权限隔离)和 Delegation Toolkit(7710/7715 需要钱包侧有成熟的权限基础设施)。如果不做 Snaps 也不做智能账户委托,EIP-2255 权限系统就是过度工程。
第二部分:标准 EOA 钱包的权限流程
用户从打开 DApp 到完成操作,经历的权限环节:发现 → 连接 → 逐次确认。
- 钱包发现(EIP-6963) — DApp 通过事件广播发现多个钱包,用户选择 provider。无权限授予。
- 连接/暴露账户(EIP-1102 → EIP-2255) — DApp 调
eth_requestAccounts,钱包弹窗让用户选择暴露哪些地址。这是第一个真正的权限行为。 - 网络切换(EIP-3085 / EIP-3326) —
wallet_switchEthereumChain/wallet_addEthereumChain,非受限方法,但钱包仍弹窗确认。 - 签名请求 —
personal_sign、eth_signTypedData_v4、eth_sendTransaction,每次调用都弹窗,无预授权机制。 - Token 授权(ERC-20 approve) — 链上层面的权限,和钱包权限机制是两回事,但用户体验上混在一起。
本质上就是一个“看门人”模型 — 钱包守着 RPC 通道的大门,决定 DApp 能不能跟账户“说上话”。一旦 DApp 拿到说话的权利,后续每个操作就退化成逐次弹窗确认。
第三部分:边界场景对比矩阵
不列“正常流程”(连接弹窗长什么样、签名弹窗展示什么 — 这些打开就能看到)。只列那些状态不一致、时序冲突、通道差异下的刁钻表现。MegaWallet 列留空待填。
账户状态
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
钱包锁定时,DApp 调 eth_accounts(已有连接权限)→ 返回 [] 还是已授权地址? |
|||||
用户切到 Account B(B 未被授权给该 DApp)→ emit B?emit []?还是不 emit、保持原授权账户? |
|||||
eth_sendTransaction 的 from 指定非当前活跃账户 → 拒绝、忽略 from、还是切过去? |
网络状态
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
tx 带的 chainId 和钱包当前链不匹配 → 在当前链执行?拒绝?自动切? |
|||||
| 钱包是“全网络可选”心智模型 → 连接时报给 DApp 的 chainId 是哪个? | |||||
| 两个 DApp 同时连接,分别期望不同链 → 全局单链互相影响,还是 per-DApp 隔离? |
并发 / 时序
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
| DApp 连续快速发多个签名请求 → 排队?覆盖?并行弹窗? | |||||
| 上一笔交易 pending 中,DApp 发新交易 → nonce 管理策略(自动递增?等待?) | |||||
wallet_switchEthereumChain 和 eth_sendTransaction 几乎同时到达 → 竞态处理 |
硬件钱包
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
| 硬件钱包签名超时(用户长时间不操作设备)→ 超时时间?DApp 收到什么? | |||||
| DApp 发 EIP-712 签名但设备固件不支持 → 提示开盲签?直接失败?软件端降级处理? | |||||
| DApp 连发多笔交易 → 每笔都要设备确认?还是可以批量? |
WalletConnect 模式
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
| 手机钱包 App 被系统杀掉 / 后台冻结 → DApp 发请求的表现和超时策略 | |||||
| WC 会话过期 → DApp 继续发请求 → 错误处理和自动重连策略 | |||||
| 用户在手机端切了账户/链 → 桌面 DApp 是否通过 session update 感知? | |||||
| namespace 协商:DApp 要求 eip155 + solana,钱包只支持 eip155 → 部分通过还是整体拒绝? |
容错
| 场景 | MegaWallet | MetaMask | OKX Wallet | Rainbow | Coinbase Wallet |
|---|---|---|---|---|---|
| gas 估算失败(合约 revert)→ 仍允许用户强制发送?还是直接拦截? | |||||
| RPC 节点不可达 → 降级/重试/切备用节点策略 |
第四部分:关键设计争议点分析
矩阵里几个场景背后有根本性的架构分歧,不是简单的“A 还是 B”能讲清楚的。这里展开讨论每个争议点的本质、行业做法、以及对 MegaWallet 的影响。
1. 切账户时 DApp 感知策略:全局活跃 vs per-DApp 绑定
这是最核心的架构分歧,直接决定了账户状态相关场景的所有行为。
全局活跃账户模型(MetaMask): 钱包维护一个全局“当前账户”。DApp 看到的永远是这个当前账户(前提是有权限)。用户在钱包里切账户,所有已连接 DApp 都受影响。
- 切到已授权账户 B → emit B
- 切到未授权账户 C → emit
[],DApp 视角等于断开 - 问题:用户只是想在钱包里看看另一个账户的余额,DApp 那边就“断了”。体验割裂,用户困惑
per-DApp 绑定模型: 每个 DApp 有自己绑定的账户,钱包内部切账户不影响任何 DApp 连接。只有用户在 DApp 内主动操作(比如点“切换账户”)才会改变绑定关系。
- 用户在钱包里切账户 → DApp 无感知,不 emit
- DApp 始终看到自己绑定的那个账户
- 问题:用户可能期望“我切了账户,DApp 也跟着变”,需要在 UX 上明确引导
MegaWallet 需要决定: 如果走 per-DApp 绑定,架构上更干净(DApp 连接是稳定的),但需要提供 DApp 内切换账户的交互入口。如果走全局活跃,兼容性最好(DApp 生态默认假设 MetaMask 行为),但要接受“切账户断连接”的体验代价。
2. 钱包锁定时的权限语义:连接 ≠ 解锁
eth_accounts 在钱包锁定时返回什么,本质上是在回答一个问题:连接权限是否依赖解锁状态?
返回 [](MetaMask):
锁定 = 不暴露任何信息。即使 DApp 之前连接过,锁定后也看不到地址。安全性最高,但 DApp 无法区分“从未连接”和“已连接但锁定”。
返回已授权地址: 连接权限是持久的,锁定只影响签名能力。DApp 可以继续展示用户地址和余额(只读),签名时再要求解锁。体验更连贯,但地址始终暴露。
折中方案:
返回 [],但提供 wallet_getPermissions 让 DApp 区分“未连接”和“已连接但锁定”,从而展示不同的 UI(“连接钱包” vs “解锁钱包”)。MetaMask 支持这个,但几乎没有 DApp 用。
3. 多 DApp 多链:全局单链 vs per-DApp 链隔离
用户同时打开 Uniswap(期望 Ethereum)和 PancakeSwap(期望 BSC),钱包怎么处理?
全局单链(MetaMask):
钱包只有一个当前链。DApp A 调 wallet_switchEthereumChain 切到 BSC,DApp B 也被影响 — 收到 chainChanged 事件,突然发现自己在 BSC 上了。
- 这是 EIP-1193 原始设计的直接后果 — provider 是全局单例
- 用户体验:在一个 DApp 里切链,另一个 DApp 莫名其妙跟着变
per-DApp 链隔离: 每个 DApp 会话维护独立的链状态。DApp A 切链不影响 DApp B。
- 架构更合理,但偏离了 EIP-1193 的原始假设
- 部分 DApp 可能依赖全局链状态的行为(虽然这本身就是 bad practice)
4. eth_sendTransaction 的 from 字段冲突
DApp 发交易时指定的 from 和钱包当前活跃账户不一致。三种策略各有问题:
- 拒绝(MetaMask): 最安全,但 DApp 如果缓存了旧地址就会反复失败,用户不知道为什么
- 忽略 from,用当前账户: 危险 — DApp 以为是 A 在操作,实际是 B,可能导致资产误操作
- 自动切到 from 指定的账户: 隐式切换可能让用户困惑,且如果该账户未授权给这个 DApp 就更复杂
在 per-DApp 绑定模型下,这个问题天然缓解 — DApp 绑定的账户是稳定的,from 不一致的概率大幅降低。
5. 并发签名请求的队列策略
DApp(尤其是 DeFi 协议)可能快速连发多个签名请求(approve + swap、多步操作)。
串行队列(MetaMask): 一个弹窗处理完才弹下一个。安全但慢,用户要点 N 次确认。
并行弹窗: 同时展示多个确认弹窗。快但混乱,用户可能搞混顺序,误签。
批量确认: 将相关请求打包成一个确认界面。体验最好,但需要判断哪些请求“相关”,实现复杂。
这个决策和 nonce 管理策略强耦合 — 如果允许并行,nonce 必须预分配;如果串行,可以等上一笔确认后再取 nonce。