简介
EIP-7702(Ethereum Improvement Proposal 7702)提供了一种创新的解决方案,允许外部拥有账户(EOA)通过智能合约执行批量操作。这个提案极大地改善了用户体验,提高了交易效率,同时降低了gas成本。
核心特性
-
批量交易执行
- 在单个交易中执行多个操作
- 支持ETH转账和代币转账的组合
- 通过委托合约实现复杂的批量调用
-
委托机制
- 用户可以授权特定合约代表自己执行操作
- 保持交易的安全性和可控性
- 无需将私钥暴露给第三方
-
灵活性
- 支持多种类型的交易组合
- 可自定义执行逻辑
- 适应不同场景的需求
技术实现
智能合约实现
BatchCallDelegation 合约
BatchCallDelegation合约是实现EIP-7702的核心组件,它提供了批量执行多个调用的功能:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
contract BatchCallDelegation {
struct Call {
bytes data;
address to;
uint256 value;
}
function execute(Call[] calldata calls) external payable {
for (uint256 i = 0; i < calls.length; i++) {
Call memory call = calls[i];
(bool success,) = call.to.call{ value: call.value }(call.data);
require(success, "call reverted");
}
}
}
SimpleToken 合约
为了演示EIP-7702的实际应用,我们实现了一个简单的ERC20代币合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.28 <0.9.0;
contract SimpleToken {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _initialSupply * (10 ** uint256(_decimals));
_balances[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "SimpleToken: transfer from the zero address");
require(recipient != address(0), "SimpleToken: transfer to the zero address");
require(_balances[sender] >= amount, "SimpleToken: transfer amount exceeds balance");
unchecked {
_balances[sender] = _balances[sender] - amount;
_balances[recipient] = _balances[recipient] + amount;
}
emit Transfer(sender, recipient, amount);
}
}
客户端实现
批量转账示例
下面是一个完整的批量转账示例,展示了如何使用EIP-7702执行ETH和代币的批量转账:
import { createWalletClient, http, parseEther, parseAbi, createPublicClient, encodeFunctionData, formatEther, formatUnits } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { eip7702Actions } from "viem/experimental";
const ALICE_PK = "";
const BOB = "";
// 更新为您部署的合约地址
const BATCH_CALL_DELEGATION = "0xb764372fd28c302e29ce6f1972f06d99300de69f";
const SIMPLE_TOKEN = "0xf74f2008215a358d09fa4bd9cf4e3027cc904b1a";
const main = async () => {
console.log(sepolia.rpcUrls);
const account = privateKeyToAccount(ALICE_PK);
const clients = {
wallet: createWalletClient({ chain: sepolia, transport: http(), account }).extend(eip7702Actions()),
public: createPublicClient({ chain: sepolia, transport: http() }),
};
// 签署委托
const authorization = await clients.wallet.signAuthorization({
contractAddress: BATCH_CALL_DELEGATION,
});
const tokenAbi = parseAbi([
"function balanceOf(address) view returns (uint256)",
"function transfer(address,uint256) returns (bool)",
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalSupply() view returns (uint256)",
]);
// 获取代币信息
const tokenName = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "name",
});
const tokenSymbol = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "symbol",
});
const tokenDecimals = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "decimals",
});
console.log(`>>> 代币信息: ${tokenName} (${tokenSymbol}), 小数位: ${tokenDecimals}`);
// 检查转账前的余额
const ethBalanceBefore = await clients.public.getBalance({ address: BOB });
console.log(`>>> Bob的ETH余额 (转账前): ${formatEther(ethBalanceBefore)} ETH`);
const tokenBalanceBefore = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [BOB],
});
console.log(`>>> Bob的${tokenSymbol}余额 (转账前): ${formatUnits(tokenBalanceBefore, tokenDecimals)} ${tokenSymbol}`);
// 准备代币转账数据
const tokenTransferAmount = 100n * 10n ** BigInt(tokenDecimals);
console.log(`>>> 代币转账金额: ${formatUnits(tokenTransferAmount, tokenDecimals)} ${tokenSymbol}`);
// 编码代币转账函数调用
const tokenTransferData = encodeFunctionData({
abi: tokenAbi,
functionName: "transfer",
args: [BOB, tokenTransferAmount],
});
try {
// 检查Alice是否有足够的代币
const aliceTokenBalance = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [account.address],
});
console.log(`>>> Alice的${tokenSymbol}余额: ${formatUnits(aliceTokenBalance, tokenDecimals)} ${tokenSymbol}`);
if (aliceTokenBalance < tokenTransferAmount) {
console.log(">>> Alice没有足够的代币。尝试从合约部署者转移代币给Alice...");
// 直接调用代币合约转账给Alice
await clients.wallet.writeContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "transfer",
args: [account.address, tokenTransferAmount * 2n],
});
console.log(">>> 代币已从部署者转移给Alice");
// 更新Alice的代币余额
const updatedAliceBalance = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [account.address],
});
console.log(`>>> Alice更新后的代币余额: ${formatUnits(updatedAliceBalance, tokenDecimals)} ${tokenSymbol}`);
}
// 批量执行ETH和代币转账
console.log(">>> 执行批量转账 (ETH + 代币)...");
const batchAbi = parseAbi(["function execute((bytes data,address to,uint256 value)[])"]);
await clients.wallet.writeContract({
abi: batchAbi,
address: account.address,
functionName: "execute",
args: [
[
// ETH转账
{
data: "0x", // 空数据表示简单的ETH转账
to: BOB,
value: parseEther("0.0001"), // 转账0.0001 ETH
},
// 代币转账
{
data: tokenTransferData,
to: SIMPLE_TOKEN, // 代币合约地址
value: 0n, // 不发送ETH
},
],
],
authorizationList: [authorization],
});
console.log(">>> 批量转账成功完成");
} catch (error) {
console.error(">>> 批量转账失败:", error);
}
// 检查转账后的余额
const ethBalanceAfter = await clients.public.getBalance({ address: BOB });
console.log(`>>> Bob的ETH余额 (转账后): ${formatEther(ethBalanceAfter)} ETH`);
console.log(`>>> ETH增加了: ${formatEther(ethBalanceAfter - ethBalanceBefore)} ETH`);
const tokenBalanceAfter = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [BOB],
});
console.log(`>>> Bob的${tokenSymbol}余额 (转账后): ${formatUnits(tokenBalanceAfter, tokenDecimals)} ${tokenSymbol}`);
console.log(`>>> 代币增加了: ${formatUnits(tokenBalanceAfter - tokenBalanceBefore, tokenDecimals)} ${tokenSymbol}`);
};
main();
这个示例代码展示了如何使用EIP-7702实现批量转账功能,包括:
- ETH转账
- 代币转账
- 余额检查
- 错误处理
- 完整的日志输出
实际应用示例
在上述代码中,我们展示了如何在一个交易中同时执行ETH和代币转账:
- ETH转账:向接收方直接发送ETH
- 代币转账:调用ERC20代币合约的transfer方法
- 批量执行:通过BatchCallDelegation合约一次性完成上述操作
完整流程示例
- 检查转账前余额
const ethBalanceBefore = await clients.public.getBalance({ address: BOB });
const tokenBalanceBefore = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [BOB],
});
- 执行批量转账
// 批量执行ETH和代币转账的代码如上述"执行批量转账"部分所示
- 验证转账结果
const ethBalanceAfter = await clients.public.getBalance({ address: BOB });
const tokenBalanceAfter = await clients.public.readContract({
abi: tokenAbi,
address: SIMPLE_TOKEN,
functionName: "balanceOf",
args: [BOB],
});
总结
EIP-7702为以太坊生态系统带来了重要的改进,通过批量交易委托执行的创新机制,有效解决了EOA在进行复杂操作时的效率问题。这一提案不仅优化了用户体验,也为开发者提供了更多可能性,推动了以太坊生态的进一步发展。
通过本文提供的完整示例和详细说明,开发者可以快速理解和实现EIP-7702的功能,将其应用到自己的项目中。这种批量交易委托执行的方案,将为DApp开发带来更多可能性和更好的用户体验。