EOA钱包智能升级:基于意图的代理技能架构设计与实现
1. 项目概述当EOA钱包学会“技能”在Web3的世界里EOA外部拥有账户钱包比如我们最熟悉的MetaMask一直是用户与区块链交互的基石。它们简单、直接一个私钥对应一个地址签名、发送交易一气呵成。但这份“简单”也带来了众所周知的痛点功能单一、操作繁琐、安全脆弱。每一次与复杂智能合约的交互都像是一次手工组装用户需要精确地填写每一个参数稍有不慎交易就可能失败甚至资产受损。“Portkey-Wallet/eoa-agent-skills”这个项目正是为了解决这个核心矛盾而生。它不是一个全新的钱包而是一个为现有EOA钱包注入“智能”的技能库。你可以把它想象成给你的MetaMask安装了一个“智能助手插件包”。这个助手不再只是被动地执行你输入的原始交易数据而是能够理解你的意图并自动、安全地组合调用一系列底层合约完成一个复杂的链上操作。举个例子你想在去中心化交易所DEX用ETH购买某个ERC20代币并立即将其中一半进行质押。传统流程是1授权DEX合约使用你的ETH2执行兑换交易3授权质押合约使用新获得的代币4执行质押交易。四个步骤四次手动签名确认费时费力且容易出错。而通过集成eoa-agent-skills你的钱包可以理解“用ETH买币并质押一半”这个高级意图自动生成并安全地按顺序执行这四笔交易你只需要在最后确认一次即可。这个项目的核心价值在于它试图在不牺牲EOA钱包自主控制权私钥始终在用户手中的前提下极大地提升其可用性和功能性让普通用户的链上体验向拥有智能合约钱包的便捷性靠拢。它适合所有希望优化其DApp用户体验的开发者以及任何对下一代钱包交互范式感兴趣的技术探索者。2. 核心架构与设计哲学2.1 从“交易执行器”到“意图执行器”的范式转变传统EOA钱包的角色是“交易执行器”。用户或前端应用构造好一个标准的以太坊交易包含to,data,value等字段钱包负责签名并广播。这个过程是机械的、无状态的。eoa-agent-skills引入的是“意图执行器”范式。在这里用户表达的是一个目标状态或意图例如“我想拥有至少100个XYZ代币”而不是“请调用0xabc合约的swap函数参数是...”。这个意图会被提交给一个所谓的“求解器网络”或“技能代理”。项目的核心组件“Agent”就是这个求解器的具体实现。它内部封装了各种“技能”Skills。每个技能都是一个独立的模块专门负责完成某一类特定的链上操作比如资产兑换Swap、质押Stake、借贷Lending等。Agent的工作就是解析用户的意图从技能库中挑选并组合出一个或多个能实现该意图的原子交易序列。设计哲学的关键点在于解耦与组合解耦将复杂的区块链交互逻辑如价格查询、滑点计算、合约调用编码封装在独立的技能模块中与钱包核心的签名、发送功能分离。组合通过一个高层的、统一的意图接口允许动态组合这些技能以完成复杂、多步骤的DeFi操作。这种设计使得钱包本身变得极其轻量且可扩展。新增一个协议的支持只需要开发一个新的技能模块并注册到Agent中无需改动钱包核心。2.2 技能Skill的抽象与实现技能是该项目中最核心的构建块。一个设计良好的技能需要遵循统一的接口规范通常需要实现以下几个关键方法意图匹配matchIntent判断当前技能是否能处理用户提交的特定意图。例如一个“Uniswap V3 Swap Skill”会检查意图中是否包含资产兑换相关的描述。参数校验与生成validateAndEncode根据意图和当前链上状态通过RPC节点获取校验参数的合法性如余额是否充足、流动性是否足够并生成待签名的交易Calldata。模拟执行simulate在交易正式签名前在本地或测试节点上模拟执行生成的交易预估Gas消耗、输出结果如预计获得的代币数量并检查是否有回滚风险。这是保障用户体验和安全的关键一步。执行execute在用户确认后将签名后的交易广播上链。一个技能的内部实现本质上是对一个或多个智能合约接口的封装和业务逻辑编排。以兑换技能为例其内部需要连接去中心化交易所如Uniswap, 1inch的路由器合约。通过预言机或DEX的报价接口获取实时价格和滑点。计算最优路径可能涉及多个交易对。编码对路由器合约的swapExactTokensForTokens或类似函数的调用。注意技能模块必须是无状态的、幂等的。它不应存储用户的私钥或会话状态。所有状态如临时交易参数应由上层管理者Agent或钱包UI来维护。2.3 代理Agent的工作流与安全边界Agent是技能的管理者和调度者。其标准工作流如下接收意图从用户界面钱包插件、移动App接收一个结构化的意图对象。技能发现遍历已注册的技能列表调用每个技能的matchIntent方法筛选出所有可能处理该意图的技能。方案求解对于每个匹配的技能请求其生成交易方案包括交易Calldata、预估Gas、预期结果。有时一个意图可能有多种实现方案例如通过不同DEX兑换Agent需要对这些方案进行排序按成本最优、速度最快等。呈现与模拟将最佳方案或前几个方案的详细信息如兑换率、手续费、步骤分解呈现给用户。在用户确认前进行模拟执行确保万无一失。签名与执行用户确认后Agent将最终的交易数据传递给钱包核心进行签名。签名后技能模块的execute方法被调用以广播交易。状态监控与回执处理监控交易上链状态并在成功后可能触发后续技能在多步意图中或更新UI状态。安全边界是此架构的重中之重私钥隔离Agent和技能模块在任何情况下都不能接触用户的私钥或助记词。签名动作必须发生在受严格保护的钱包核心模块内。模拟执行强制任何交易在执行前必须经过模拟防止因参数错误导致资产损失。用户最终确认无论方案多么优化最终执行权必须交给用户通过一个清晰的确认界面展示所有关键信息如交互的合约地址、转移的资产数量。技能权限沙箱可以考虑为每个技能定义最小权限集例如只能与特定白名单合约交互防止恶意技能模块作恶。3. 关键技能模块深度解析3.1 资产兑换Swap技能不仅仅是调用路由器兑换是DeFi中最基础也是最频繁的操作。一个成熟的Swap Skill远不止是封装一个路由器合约的调用。核心流程拆解报价获取与聚合本地源首先查询集成在技能内部的多个DEX路由器的getAmountsOut或类似只读方法获取初步报价。聚合器API为了获得最优价格必须集成像1inch、ParaSwap、0x API这样的链下聚合器。它们会扫描全网的流动性可能拆分订单到多个DEX并提供最优的聚合路径。技能需要调用这些聚合器的公开API获取一个已编码好的交易数据transaction.data。价格验证将聚合器返回的报价与本地直接从主要DEX如Uniswap获取的报价进行比对如果差异过大可能意味着聚合器API被攻击或返回过时数据应拒绝该方案并告警。滑点与手续费计算动态滑点不应使用固定滑点。技能应根据代币的流动性深度、市场波动性可参考历史价格数据和交易金额占池子的比例动态计算一个建议滑点容忍度。例如交易大额低流动性代币时自动建议更高的滑点。手续费内化许多聚合器或路由器会收取协议手续费。技能需要清晰地在UI中展示净到手金额输出金额减去所有手续费而不仅仅是理论兑换量。交易编码与优化许可Approval处理如果用户是首次使用某个代币需要先授权ApproveDEX路由器使用该代币。高级的技能可以集成“免许可”或“一次性许可”逻辑例如通过EIP-2612Permit签名授权或者将授权和兑换合并到一笔元交易meta-transaction中。Gas优化对于ERC20代币在兑换前检查合约的decimals()确保金额计算正确。对于WETH/ETH兑换优先使用deposit/withdraw方法而非通用的兑换路径以节省Gas。实操心得 在实现时务必为所有外部API调用如聚合器报价设置超时和重试机制。链上状态瞬息万变一个缓慢的API响应可能导致用户最终执行时价格已不利。我们的策略是并行请求多个聚合器取最快且最优的结果并为整个报价流程设置一个总超时如3秒超时后降级为使用本地DEX路由。3.2 资产质押Staking与收益聚合Yield技能这类技能涉及将资产存入某个协议以获取收益流程相对标准但风险点不同。核心流程与风控协议集成与版本管理像Compound、Aave、Lido这样的主流协议其合约地址可能因升级而改变。技能内部必须维护一个可更新的、链ID到合约地址的映射表。必须严格区分协议的V1、V2等不同版本因为它们的接口可能不兼容。通过链上查询或预置配置来确定当前应使用的合约地址。收益数据获取与展示实时APY需要通过协议的只读函数或子图The Graph查询当前的年化收益率。注意区分“供应APY”和“借款APY”以及是浮动利率还是固定利率。待领取奖励对于有额外激励的池子如流动性挖矿需要查询pendingRewards等函数并计算可领取的代币价值。展示透明化必须清晰展示收益的计算基础是浮动利率还是历史平均值、更新频率以及任何潜在的费用如提款费、绩效费。资产存入/取出流程余额检查在执行stake或supply前不仅要检查用户钱包余额还要检查协议的可用流动性例如借贷协议的剩余可借额度。份额代币如cToken, aToken, stETH处理存入后用户收到的是代表份额的衍生代币。技能需要能查询和展示这些衍生代币的余额及对应的底层资产价值通过汇率exchangeRateCurrent。取回Withdraw/Redeem需要处理两种模式按份额取回赎回cToken和按底层资产数量取回。通常后者对用户更直观技能内部需要做换算。常见问题排查交易失败“insufficient liquidity”这通常发生在协议池子流动性耗尽时。技能在模拟阶段就应检查协议的可用余额并在UI上明确提示风险。对于借贷协议还需检查健康因子Health Factor防止取出资产导致清算。收益显示为0或不准首先检查是否正确连接到了协议的读写合约而非代理或逻辑合约。其次确认查询收益的函数是否需要先手动触发一个accrueInterest或updatePool交易来更新状态有些协议需要。在实践中我们建议技能定期如每10个区块主动调用一次状态更新函数或提示用户先执行一笔更新交易。3.3 跨链交互Cross-chain技能初探虽然eoa-agent-skills可能最初聚焦于单链但跨链意图是必然需求。一个跨链技能可以管理通过官方桥、第三方跨链桥或原子交换实现的资产转移。实现要点桥接器抽象层定义统一的接口BridgeAdapter包含quote获取费用、时长、buildTransaction构建源链交易、getStatus查询跨链状态等方法。然后为每个支持的跨链桥如Hop, Synapse, Stargate, 官方桥实现这个适配器。源链与目标链的协调用户意图是“将X链的A资产转到Y链”。技能需要在源链上构建锁定资产或销毁资产的交易。生成一个目标链的预期收款地址通常是同一个EOA地址。向用户清晰展示两笔交易如果需要源链发送交易以及可能在目标链上领取资产的交易有些桥是自动到账有些需要手动领取。状态监控与超时处理跨链交易耗时长几分钟到几小时。技能需要持久化监控任务定期通过桥接器提供的API或查询目标链事件检查跨链状态。如果超过预期时间仍未完成应有明确的超时处理和客服指引路径。重要提示跨链技能是安全重灾区。必须严格验证桥接合约地址防止网络钓鱼。只集成经过长时间审计、有良好声誉的桥接协议。在UI上必须用最醒目的方式提示用户“跨链有风险请小额测试”。4. 实战构建一个简单的代理与技能让我们抛开复杂的框架从零构建一个极简版的“代理-技能”系统以理解其精髓。我们将实现一个“发送原生币Send Native Token”技能。4.1 环境准备与基础结构我们使用Node.js和TypeScript以及ethers.js库。mkdir eoa-agent-demo cd eoa-agent-demo npm init -y npm install typescript ts-node ethers^5.0.0 dotenv --save npm install types/node --save-dev创建项目结构src/ ├── skills/ │ ├── Skill.ts // 技能抽象接口 │ └── SendNativeSkill.ts // 发送原生币技能实现 ├── Agent.ts // 代理核心 ├── types.ts // 类型定义 └── index.ts // 入口文件首先定义核心类型和接口// src/types.ts export interface Intent { type: string; // 例如send-native, swap-tokens parameters: Recordstring, any; // 意图参数 } export interface TransactionCandidate { to: string; data: string; value: bigint; chainId: number; gasLimit?: bigint; description: string; // 给用户看的描述 } export interface Skill { // 技能唯一标识 name: string; // 是否能处理该意图 canHandle(intent: Intent): boolean; // 生成交易候选方案 generateTransaction(intent: Intent, context: any): PromiseTransactionCandidate; // 执行交易通常由上层调用此处只做编码 encodeExecution(txCandidate: TransactionCandidate): Promise{to: string, data: string, value: bigint}; }4.2 实现“发送原生币”技能这是一个最简单的技能它处理类型为send-native的意图参数包含recipient收款地址和amount金额。// src/skills/SendNativeSkill.ts import { Skill, Intent, TransactionCandidate } from ../types; import { ethers } from ethers; export class SendNativeSkill implements Skill { name SendNativeSkill; canHandle(intent: Intent): boolean { return intent.type send-native; } async generateTransaction(intent: Intent, context: { provider: ethers.providers.Provider, signerAddress: string }): PromiseTransactionCandidate { const { recipient, amount } intent.parameters; // 基础校验 if (!ethers.utils.isAddress(recipient)) { throw new Error(Invalid recipient address: ${recipient}); } const value ethers.utils.parseEther(amount.toString()); // 假设金额以ETH为单位 // 获取当前网络链ID const network await context.provider.getNetwork(); // 估算Gas发送ETH的Gas相对固定但这里演示流程 const gasEstimate await context.provider.estimateGas({ from: context.signerAddress, to: recipient, value: value }).catch(() BigInt(21000)); // 如果失败使用基础Gas 21000 return { to: recipient, data: 0x, // 发送原生币data为空 value: value, chainId: network.chainId, gasLimit: gasEstimate * BigInt(120) / BigInt(100), // 估算Gas上浮20% description: Send ${amount} ETH to ${recipient} }; } async encodeExecution(txCandidate: TransactionCandidate): Promise{to: string, data: string, value: bigint} { // 对于简单的发送交易直接返回 return { to: txCandidate.to, data: txCandidate.data, value: txCandidate.value }; } }4.3 实现简易代理Agent代理负责管理技能并处理意图。// src/Agent.ts import { Skill, Intent, TransactionCandidate } from ./types; export class SimpleAgent { private skills: Skill[] []; registerSkill(skill: Skill): void { this.skills.push(skill); console.log(Skill registered: ${skill.name}); } async processIntent(intent: Intent, context: any): PromiseTransactionCandidate[] { const candidates: TransactionCandidate[] []; for (const skill of this.skills) { if (skill.canHandle(intent)) { try { const candidate await skill.generateTransaction(intent, context); candidates.push(candidate); console.log(Skill ${skill.name} generated a candidate.); } catch (error) { console.error(Skill ${skill.name} failed to generate transaction:, error); } } } if (candidates.length 0) { throw new Error(No skill found to handle intent type: ${intent.type}); } // 这里可以添加排序逻辑例如按Gas成本最低排序 return candidates; } }4.4 集成测试与运行创建一个入口文件来测试整个流程// src/index.ts import { SimpleAgent } from ./Agent; import { SendNativeSkill } from ./skills/SendNativeSkill; import { ethers } from ethers; import * as dotenv from dotenv; dotenv.config(); async function main() { // 1. 初始化代理和技能 const agent new SimpleAgent(); const sendSkill new SendNativeSkill(); agent.registerSkill(sendSkill); // 2. 模拟用户意图发送0.01 ETH到一个地址 const intent { type: send-native, parameters: { recipient: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, // 一个测试地址 amount: 0.01 } }; // 3. 模拟上下文这里用JsonRpcProvider实际应用是连接用户钱包 const provider new ethers.providers.JsonRpcProvider(process.env.RPC_URL || http://localhost:8545); const dummySignerAddress 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // Hardhat默认第一个账户 const context { provider, signerAddress: dummySignerAddress }; // 4. 处理意图 try { const transactions await agent.processIntent(intent, context); console.log(\n--- Generated Transaction Candidates ---); transactions.forEach((tx, idx) { console.log(Option ${idx 1}:); console.log( To: ${tx.to}); console.log( Value: ${ethers.utils.formatEther(tx.value)} ETH); console.log( Gas Limit: ${tx.gasLimit}); console.log( Description: ${tx.description}); console.log(---); }); // 5. 假设用户选择了第一个方案进行编码实际应由钱包签名 const selectedTx transactions[0]; const encodedTx await sendSkill.encodeExecution(selectedTx); console.log(\n--- Encoded Transaction for Signing ---); console.log(encodedTx); // 在实际钱包中此处应将 encodedTx 传递给钱包的 signTransaction/sendTransaction 方法 } catch (error) { console.error(Failed to process intent:, error); } } main();运行此脚本需配置RPC_URL环境变量指向一个以太坊测试网节点你将看到代理成功生成了一个发送ETH的交易候选方案。这个极简示例揭示了eoa-agent-skills项目的核心思想通过可插拔的技能模块将高级意图转化为可执行的链上交易。5. 安全考量、挑战与最佳实践5.1 核心安全威胁与缓解措施恶意技能模块威胁一个被恶意篡改或植入的后门技能可能诱导用户签署转移所有资产的交易。缓解建立严格的技能审核与签名机制。钱包应只加载经过官方或社区多重签名验证的技能。技能代码应开源并鼓励审计。在运行时技能应运行在受限的沙箱环境中如Web Worker无法直接访问主线程的敏感对象。前端钓鱼与界面欺骗威胁攻击者伪造钱包UI或在DApp中嵌入恶意iframe篡改交易参数如收款地址、金额。缓解钱包扩展或App应提供“交易详情”的完整、不可篡改的视图并高亮显示关键参数变化如合约地址、代币数量。推广使用EIP-712结构化签名让用户签署人类可读的消息。技能生成的交易描述必须标准化、模板化防止注入攻击。模拟执行与状态预判失败威胁模拟执行的环境与真实主网存在状态差异如依赖的预言机价格延迟导致模拟成功但真实执行失败或结果迥异。缓解模拟执行应尽可能在接近主网状态的节点如存档节点上进行。对于高度依赖链下数据的操作如DEX兑换必须设置严格的最后期限deadline参数并提示用户价格波动的风险。采用“检查-生效”模式在交易中包含必要的条件检查。私钥与签名安全威胁这是EOA钱包的固有风险。代理/技能架构本身不能解决私钥泄露问题。缓解鼓励用户使用硬件钱包。在移动端确保私钥存储在安全的加密存储中。绝不通过网络传输私钥或助记词。5.2 性能与用户体验的挑战技能发现与方案求解的延迟当注册的技能很多时串行调用每个技能的canHandle和generateTransaction可能很慢。优化采用并行处理。对技能进行预分类或打标签根据意图类型快速过滤。对耗时的外部API调用如聚合器报价设置竞争性请求取最快有效结果。交易模拟的成本与速度每笔交易在本地模拟都会消耗RPC调用对于公共节点可能有限制且慢。优化自建或使用高质量、高并发的RPC节点服务如Infura, Alchemy。对于复杂的多步意图可以考虑使用专门的“模拟网络”或Tenderly等服务的API进行批量模拟。状态管理复杂性一个多步意图如“循环贷”涉及多个中间状态和条件分支。优化引入明确的状态机来管理多步意图的生命周期。每一步执行后更新状态并决定下一步是继续执行另一个技能还是等待用户输入如确认中间结果。5.3 开发与集成的建议技能接口标准化虽然项目可能定义了内部接口但推动社区采用类似EIP的标准化技能接口例如定义标准的意图格式和技能发现协议将极大促进生态发展。测试策略单元测试对每个技能的generateTransaction、参数校验等逻辑进行充分测试。集成测试在分叉的主网环境如使用Hardhat fork中测试技能与真实合约的交互。端到端测试模拟完整用户流程从意图提交到交易广播测试整个代理和UI的协作。错误处理与用户反馈技能应提供丰富、可读的错误信息不仅仅是Revert原因。例如“兑换失败因为滑点超过5%。当前市场波动较大请尝试提高滑点容忍度或减少交易金额。”代理应能捕获并汇总所有技能的错误向用户呈现清晰的解决方案而不是堆砌技术栈信息。可升级性与治理考虑如何安全地升级已注册的技能。可以通过代理合约可升级技能注册表的方式或者由多签钱包管理技能列表的更新。对于关键参数如默认滑点、支持的链列表应允许用户在一定范围内自定义。Portkey-Wallet/eoa-agent-skills项目描绘了一个让EOA钱包变得更智能、更友好的未来图景。它的成功不仅取决于技术实现更依赖于建立一个安全、开放、标准化的技能开发生态。对于开发者而言现在是深入理解意图为中心交互模型、并开始构建自己专属技能的最佳时机。从解决一个具体的、高频的链上操作痛点开始你将能亲手参与到重塑钱包用户体验的进程之中。