引言
兄弟们,如果你还在为切不完的跨链桥、交不起的多链 Gas 费、以及反人类的"各链储备原生代币"搞得焦头烂额,那说明你还没看清下一轮牛市的密码------链抽象(Chain Abstraction)与智能体网络(Agentic Web) 。
在 NEAR Protocol 的生态里,用户早就过上了"只需动动嘴(自然语言/链下意图),后台全自动跨链结算"神仙日子。凭什么 EVM 用户就得当苦力?
今天咱们直接上硬菜!抛弃泛泛而谈的宏大叙事,直接用生产级代码说话:带你用 Solidity 0.8.28 + OpenZeppelin V5 ,在 EVM 链上硬核复刻一套类 NEAR 的免 Gas 意图(Intent)清算引擎与 AI 智能体结算枢纽!
一、 为什么 EVM 需要"类 NEAR"的意图架构?
传统的 EVM 交互逻辑是"命令式"的:用户必须自己计算 Gas、自己调用合约、自己承担滑点。而 NEAR 开辟的 Intents 范式 彻底颠覆了这一点:
- 人类可读账户: 结合 ERC-4337 与 ENS,告别冗长冷酷的
0x,拥抱如alice.eth的丝滑体验。 - 免 Gas 与极简跨链: 用户只管在链下签个"意图(Intent)",底层的 Solver(求解器) 或 AI 节点抢着代付 Gas 并塞回资产。
- 隐私 AI 自治执行: 核心合约化身公正的"裁判长",结合 TEE(可信执行环境) ,让链下 AI 智能体在绝对安全的环境下完成复杂的多链路由。
话不多说,直接上核心合约!
二、 核心合约实战:NearStyleIntentExecutor
基于最新的 Solidity 0.8.28 与 OpenZeppelin V5 模块化标准,我们编写了这份兼顾极致安全与高性能的清算合约。
js
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
// 引入 OpenZeppelin V5 标准库
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Nonces.sol";
/**
* @title NearStyleIntentExecutor
* @notice 模拟 NEAR 链抽象与 AI 意图的核心结算合约
* @dev 基于 Solidity 0.8.28 与 OpenZeppelin V5 编写
*/
contract NearStyleIntentExecutor is EIP712, AccessControl, Nonces {
using SafeERC20 for IERC20;
// 定义角色:Solver(求解器)或 AI 智能体,有权在链上提交并执行用户的意图
bytes32 public constant SOLVER_ROLE = keccak256("SOLVER_ROLE");
// EIP-712 结构化哈希:定义用户"意图"的数据结构
bytes32 public constant INTENT_TYPEHASH = keccak256(
"Intent(address user,address fromToken,uint256 fromAmount,address toToken,uint256 minToAmount,uint256 fee,uint256 deadline,uint256 nonce)"
);
// 结构体:用户链下意图数据
struct Intent {
address user; // 发出意图的用户
address fromToken; // 用户支付的资产
uint256 fromAmount; // 用户支付的资产数量
address toToken; // 用户期望获得的资产
uint256 minToAmount; // 用户要求的最低获得数量(防滑点)
uint256 fee; // 支付给 Solver/AI 的小费(从 fromToken 中扣除)
uint256 deadline; // 意图截止时间戳
}
// 事件:意图执行成功
event IntentExecuted(
bytes32 indexed intentHash,
address indexed user,
address indexed solver,
address fromToken,
address toToken,
uint256 fromAmount,
uint256 toAmount
);
/**
* @dev 构造函数,初始化 EIP-712 域和权限控制
*/
constructor(address admin) EIP712("NearStylePlatform", "1.0.0") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(SOLVER_ROLE, admin); // 默认管理员兼任第一个 Solver
}
/**
* @notice 由经授权的 Solver 或 AI 智能体调用,在链上结算用户的离线意图
* @param intent 用户签名的意图结构体
* @param signature 用户的 EIP-712 密码学签名
* @param actualToAmount Solver 实际塞回给用户的目标代币数量
*/
function executeIntent(
Intent calldata intent,
bytes calldata signature,
uint256 actualToAmount
) external onlyRole(SOLVER_ROLE) {
// 1. 检查截止时间
if (block.timestamp > intent.deadline) revert("Intent expired");
// 2. 检查滑点防御
if (actualToAmount < intent.minToAmount) revert("High slippage or insufficient output");
// 3. 校验 Nonce 并递增(防止重放攻击)- 使用 OZ V5 的 _useNonce
uint256 currentNonce = _useNonce(intent.user);
// 4. 计算 EIP-712 哈希
bytes32 structHash = keccak256(
abi.encode(
INTENT_TYPEHASH,
intent.user,
intent.fromToken,
intent.fromAmount,
intent.toToken,
intent.minToAmount,
intent.fee,
intent.deadline,
currentNonce
)
);
bytes32 hash = _hashTypedDataV4(structHash);
// 5. 验证签名者是否为发出意图的用户本身
address signer = ECDSA.recover(hash, signature);
if (signer != intent.user) revert("Invalid intent signature");
// 6. 价值流转结算 (Asset Flow)
// a. 从用户处扣除原始资产(扣除额包含给 Solver 的小费)
IERC20(intent.fromToken).safeTransferFrom(intent.user, _msgSender(), intent.fromAmount);
// b. Solver / AI 智能体将目标资产注入给用户(完成了跨链/DEX路由后的结果)
IERC20(intent.toToken).safeTransferFrom(_msgSender(), intent.user, actualToAmount);
// 释放执行事件
emit IntentExecuted(hash, intent.user, _msgSender(), intent.fromToken, intent.toToken, intent.fromAmount, actualToAmount);
}
}
核心安全机制解密(OpenZeppelin V5 杀手级特性):
- 原子化 Nonce 追踪 (
_useNonce): 告别手写计数器的蛮荒时代。V5 内置模块在每次意图完成时自动加锁自增,同一张离线签名在链上永久失效,防重放直接拉满。 - 可信隔离屏障 (
AccessControl+ TEE): 为什么不能像 UniswapX 那样公开给全网撮合?因为我们要复现 NEAR 的 AI 智能体网络!只有跑在硬件隔离区(TEE)里的白名单 Solver 节点才能调用,从根源上掐断了恶意抢跑(Front-running)和夹子攻击。 - 现代代币防御 (
SafeERC20): 完美适配各种奇葩实现的非标准 ERC-20,防范转账不返回 bool 导致的恶意吞钱。
三、 生产级集成测试:用 Viem 验证闭环
光说不练假把式,我们直接上现代化的 TypeScript 测试脚本(基于 Viem 与 Node.js 原生测试框架),模拟真实用户签名、Solver 代付执行的全流程:
- 测试用例:NearStyleIntentExecutor Integration Test
- 场景 1:链抽象核心流转 --- 用户链下签名意图,Solver 成功在链上代付 Gas 执行
- 场景 2:权限拦截 --- 未被授权 SOLVER_ROLE 的恶意节点无法提交并结算用户意图
- 场景 3:防重放攻击 --- 同一个用户签名意图无法被执行第二次
- 场景 4:滑点防御 --- 当实际塞给用户的代币少于用户意图的最低要求时回滚
js
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseEther, getAddress, maxUint256 } from "viem";
import { network } from "hardhat";
describe("NearStyleIntentExecutor Integration Test", function () {
/**
* 部署及初始化脚手架
*/
async function deployFixture() {
const { viem } = await (network as any).connect();
const [admin, user, solver, randomUser] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
// 1. 部署 NearStyleIntentExecutor 结算合约,并指定 admin
const executor = await viem.deployContract("NearStyleIntentExecutor", [admin.account.address]);
// 2. 为测试准备两个模拟 ERC20 代币:用户支付的 (fromToken) 和 期望获得的 (toToken)
// 假设使用参考脚本中的 BoykaYuriToken,或任意符合标准的 MockERC20
const fromToken = await viem.deployContract("BoykaYuriToken", [admin.account.address, admin.account.address]);
const toToken = await viem.deployContract("BoykaYuriToken", [admin.account.address, admin.account.address]);
// 3. 给 Solver 角色赋权(让 solver 钱包拥有 SOLVER_ROLE)
const SOLVER_ROLE = await executor.read.SOLVER_ROLE();
await executor.write.grantRole([SOLVER_ROLE, solver.account.address], { account: admin.account });
// 4. 为用户和 Solver 分配初始代币并授权
const initialAmount = parseEther("1000");
await fromToken.write.transfer([user.account.address, initialAmount], { account: admin.account });
await toToken.write.transfer([solver.account.address, initialAmount], { account: admin.account });
// 用户授权给 Executor 扣款,Solver 授权给 Executor 扣款结算
await fromToken.write.approve([executor.address, maxUint256], { account: user.account });
await toToken.write.approve([executor.address, maxUint256], { account: solver.account });
const chainId = BigInt(await publicClient.getChainId());
return {
executor,
fromToken,
toToken,
admin,
user,
solver,
randomUser,
publicClient,
chainId,
};
}
/**
* 辅助函数:在链下为用户生成符合 EIP-712 标准的意图签名
*/
async function signUserIntent(params: {
client: any;
verifyingContract: `0x${string}`;
chainId: bigint;
intent: any;
nonce: bigint;
}) {
const domain = {
name: "NearStylePlatform",
version: "1.0.0",
chainId: Number(params.chainId),
verifyingContract: params.verifyingContract,
} as const;
const types = {
Intent: [
{ name: "user", type: "address" },
{ name: "fromToken", type: "address" },
{ name: "fromAmount", type: "uint256" },
{ name: "toToken", type: "address" },
{ name: "minToAmount", type: "uint256" },
{ name: "fee", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "nonce", type: "uint256" },
],
} as const;
const message = {
user: params.intent.user,
fromToken: params.intent.fromToken,
fromAmount: params.intent.fromAmount,
toToken: params.intent.toToken,
minToAmount: params.intent.minToAmount,
fee: params.intent.fee,
deadline: params.intent.deadline,
nonce: params.nonce,
} as const;
return await params.client.signTypedData({
account: params.client.account,
domain,
types,
primaryType: "Intent",
message,
});
}
it("场景 1:链抽象核心流转 --- 用户链下签名意图,Solver 成功在链上代付 Gas 执行", async function () {
const { executor, fromToken, toToken, user, solver, publicClient, chainId } = await deployFixture();
const fromAmount = parseEther("100");
const minToAmount = parseEther("95");
const actualToAmount = parseEther("97"); // 满足并优于最低滑点要求
const fee = parseEther("2"); // 激励给 Solver 的小费
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1小时后过期
const intent = {
user: user.account.address,
fromToken: fromToken.address,
fromAmount,
toToken: toToken.address,
minToAmount,
fee,
deadline,
};
// 获取当前 Nonce
const nonce = await executor.read.nonces([user.account.address]);
// 用户在链下进行 EIP-712 签名
const signature = await signUserIntent({
client: user,
verifyingContract: executor.address,
chainId,
intent,
nonce,
});
// 记录执行前各方的资产余额
const userFromBefore = await fromToken.read.balanceOf([user.account.address]);
const userToBefore = await toToken.read.balanceOf([user.account.address]);
const solverFromBefore = await fromToken.read.balanceOf([solver.account.address]);
const solverToBefore = await toToken.read.balanceOf([solver.account.address]);
// Solver 拿着用户的签名在链上触发执行(Gas 费由 solver 的钱包地址扣除)
await executor.write.executeIntent([intent, signature, actualToAmount], {
account: solver.account,
});
// 验证资产清算逻辑是否准确
assert.equal(
await fromToken.read.balanceOf([user.account.address]),
userFromBefore - fromAmount,
"用户应被扣除指定的 input 资产"
);
assert.equal(
await toToken.read.balanceOf([user.account.address]),
userToBefore + actualToAmount,
"用户应收到 Solver 注入的 output 资产"
);
assert.equal(
await fromToken.read.balanceOf([solver.account.address]),
solverFromBefore + fromAmount,
"Solver 应全额接收用户的 input 资产(包含小费)"
);
assert.equal(
await toToken.read.balanceOf([solver.account.address]),
solverToBefore - actualToAmount,
"Solver 的 output 资产应相应扣除"
);
// 验证 OpenZeppelin V5 的 Nonce 自增
const newNonce = await executor.read.nonces([user.account.address]);
assert.equal(newNonce, nonce + 1n, "执行成功后 Nonce 应自增 1");
});
it("场景 2:权限拦截 --- 未被授权 SOLVER_ROLE 的恶意节点无法提交并结算用户意图", async function () {
const { executor, fromToken, toToken, user, randomUser, chainId } = await deployFixture();
const intent = {
user: user.account.address,
fromToken: fromToken.address,
fromAmount: parseEther("10"),
toToken: toToken.address,
minToAmount: parseEther("9"),
fee: parseEther("1"),
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
};
const signature = await signUserIntent({
client: user,
verifyingContract: executor.address,
chainId,
intent,
nonce: 0n,
});
// 恶意未经授权的外部用户调用应当被 AccessControl 拒绝
await assert.rejects(
async () => {
await executor.write.executeIntent([intent, signature, parseEther("9.5")], {
account: randomUser.account,
});
},
/AccessControlUnauthorizedAccount/,
"非允许的 Solver 不允许触发意图清算"
);
});
it("场景 3:防重放攻击 --- 同一个用户签名意图无法被执行第二次", async function () {
const { executor, fromToken, toToken, user, solver, chainId } = await deployFixture();
const intent = {
user: user.account.address,
fromToken: fromToken.address,
fromAmount: parseEther("10"),
toToken: toToken.address,
minToAmount: parseEther("9"),
fee: parseEther("1"),
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
};
const signature = await signUserIntent({
client: user,
verifyingContract: executor.address,
chainId,
intent,
nonce: 0n,
});
// 第一次提交:成功
await executor.write.executeIntent([intent, signature, parseEther("9.5")], { account: solver.account });
// 第二次恶意重放同一笔签名:应当失败(因为 Nonce 已经变成了 1,与签名中的 0 不符)
await assert.rejects(
async () => {
await executor.write.executeIntent([intent, signature, parseEther("9.5")], { account: solver.account });
},
/Invalid intent signature/,
"同一意图被重放时因签名校验不匹配应该被拦截"
);
});
it("场景 4:滑点防御 --- 当实际塞给用户的代币少于用户意图的最低要求时回滚", async function () {
const { executor, fromToken, toToken, user, solver, chainId } = await deployFixture();
const intent = {
user: user.account.address,
fromToken: fromToken.address,
fromAmount: parseEther("10"),
toToken: toToken.address,
minToAmount: parseEther("9.5"), // 用户卡死:最低接受 9.5
fee: parseEther("1"),
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
};
const signature = await signUserIntent({
client: user,
verifyingContract: executor.address,
chainId,
intent,
nonce: 0n,
});
// Solver 试图恶劣滑点恶意扣留,只塞回 9.0 个代币
await assert.rejects(
async () => {
await executor.write.executeIntent([intent, signature, parseEther("9.0")], { account: solver.account });
},
/High slippage or insufficient output/,
"滑点超出用户预期应予以安全回滚"
);
});
});
四、 总结与未来展望
通过这套方案,我们成功把 NEAR 链抽象最精髓的 "Intent-Centric(以意图为中心)" 架构在 EVM 链上跑通了。
在实战中,你可以把这套合约部署在 Arbitrum、Base 等高性能 L2 上,甚至结合 Fhenix、Inco 等全同态加密(FHE)隐私公链。当用户只需要在前端输入一句话:"帮我把账户里的 100 U 换成收益最高的主流资产",后台由 TEE 中的 AI 智能体与 Solver 网络无缝接管跨链、路由与清算------这,就是属于智能体经济(Agentic Economy)的绝对未来。