关注我,带你转型Web3开发,分享我从2018年入行区块链以来的开发经验(DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch)
嘿,你有没有想过,自己的普通钱包有一天能像智能合约那样“聪明”起来?我最近花了两天时间研究了一下EIP-7702的实现原理,从钱包怎么发交易到EVM怎么执行,总算搞明白了。写下来分享给你,希望你也能跟我一样,每天进步一点点!
EIP-7702是以太坊账户抽象领域的一个新玩法,由V神(Vitalik Buterin)提出,已经被敲定放进以太坊的下一次大升级——Pectra里(2025年3月5日,测试网络Sepolia已经升级)。
简单来说,它能让你的普通钱包(也就是EOA,外部拥有账户)在发一笔交易时,临时“变身”成智能合约,干点复杂的事儿,用完就变回去。这听起来是不是挺酷?不仅用起来方便,还能给以太坊生态带来更多可能性。
在EIP-7702之前,以太坊的账户分两种:一种是你用私钥控制的EOA,比如你常用的钱包地址,只能转转账、调调合约;另一种是合约账户,部署了代码,能自己跑逻辑。EOA虽然简单,但功能有限,想干点高级的事儿就得靠合约账户。而EIP-7702来了之后,你的EOA也能临时“升级”,比如:
这些功能是不是听着就很实用?接下来我带你看看EIP-7702是怎么实现的,别担心,我会尽量讲得简单点。

EIP-7702的核心就是让你的EOA通过一笔特殊交易临时跑智能合约代码。整个过程是这样的:
0x4(别急,后面会细说);data字段里拿出要执行的合约代码delegatecall)到指定的合约代码,跑完逻辑后恢复原状关键就在这个“临时”上,你的钱包在这笔交易里能跑代码,用完就恢复原样,既灵活又不失安全性。
EIP-7702利用EIP-2718的交易类型框架,引入了新的0x4交易类型,geth等客户端也支持了它。
你可能知道,以太坊之前有传统交易0x00,还有EIP-1559的动态手续费交易0x02,现在0x4是EIP-7702的专属标识。在代码里大概长这样:
const (
LegacyTxType = 0x00 // 老式交易
AccessListTxType = 0x01 // 访问列表交易
DynamicFeeTxType = 0x02 // 动态手续费交易
BlobTxType = 0x03 // Blob交易
SetCodeTxType = 0x04 // EIP-7702的新玩法
)
你的钱包得支持造这种0x4交易,EVM看到这个类型就知道该怎么处理了。
假设你有个需求:从Alice发1 ETH给Bob,转100个MMT代币给Bob,再送一个NFT(MyDog,ID为1)给Frank。
简单来说,钱包把你的三件事打包成一段代码,塞进0x4交易发出去。
先看个简单例子:
// 你想干的三件事
const calls = [
{ to: 'Bob地址', value: '1 ETH', data: '转账' }, // 转1 ETH
{ to: 'MMT代币地址', value: 0, data: '转100 MMT给Bob' }, // 转代币
{ to: 'NFT地址', value: 0, data: '送NFT给Frank' } // 转NFT
]
// 钱包把这些打包成一笔0x4交易
const tx = {
type: '0x4',
to: '你的地址',
data: '上面那堆事的代码',
authorization: '你的签名'
}
再具体一些是这样的:
// 导入必要的库
import { createWalletClient, http, parseEther, encodeFunctionData } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { eip7702Actions } from 'viem/experimental'
import { mainnet } from 'viem/chains'
// 创建账户和客户端
const aliceAccount = privateKeyToAccount('0xAlicePrivateKey')
const walletClient = createWalletClient({
account: aliceAccount,
chain: mainnet,
transport: http()
}).extend(eip7702Actions())
// 批量调用合约地址(假设的)
const BATCH_CALL_ADDRESS = '0x1234567890123456789012345678901234567890'
// 目标地址和合约地址
const BOB_ADDRESS = '0xBobAddress'
const FRANK_ADDRESS = '0xFrankAddress'
const MMT_TOKEN_ADDRESS = '0xMMTTokenAddress'
const MYDOG_NFT_ADDRESS = '0xMyDogNFTAddress'
// 1. 创建授权对象
const batchCallAuthorization = await walletClient.signAuthorization({
contractAddress: BATCH_CALL_ADDRESS
})
// 2. 构建批量调用数据
// ETH转账
const ethTransferCall = {
to: BOB_ADDRESS,
value: parseEther('1'), // 1 ETH
data: '0x' // 普通转账不用额外数据
}
// 代币转账
const tokenTransferCall = {
to: MMT_TOKEN_ADDRESS,
value: 0n, // 不发ETH
data: encodeFunctionData({
abi: [{ name: 'transfer', type: 'function', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] }],
functionName: 'transfer',
args: [BOB_ADDRESS, parseEther('100')] // 转100个MMT
})
}
// NFT转移
const nftTransferCall = {
to: MYDOG_NFT_ADDRESS,
value: 0n,
data: encodeFunctionData({
abi: [{ name: 'transferFrom', type: 'function', inputs: [{ name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'tokenId', type: 'uint256' }], outputs: [] }],
functionName: 'transferFrom',
args: [aliceAccount.address, FRANK_ADDRESS, 1n] // 转ID=1的NFT
})
}
// 3. 打包批量调用
const batchCallData = encodeFunctionData({
abi: [{ name: 'execute', type: 'function', inputs: [{ name: 'calls', type: 'tuple[]', components: [{ name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'data', type: 'bytes' }] }], outputs: [] }],
functionName: 'execute',
args: [[ethTransferCall, tokenTransferCall, nftTransferCall]]
})
// 4. 发送EIP-7702交易
try {
const txHash = await walletClient.sendTransaction({
authorizationList: [batchCallAuthorization],
batch: [{
to: aliceAccount.address, // 指向自己
data: batchCallData, // 批量调用数据
value: parseEther('1') // 包含1 ETH用于转账
}]
})
console.log('交易已发送,哈希:', txHash)
} catch (error) {
console.error('交易失败:', error)
}
这段代码干了啥?
交易发到链上后,EVM会让Alice的EOA临时变成合约账户,跑这段批量调用的代码,执行完再变回来。
完整代码可以去我的仓库瞅瞅: https://github.com/vsiryxm/eip-7702-Study/。
交易发到EVM这边时,会经历几个步骤,我们结合Alice的例子仔细聊聊。
EVM先检查签名,确保这交易真是Alice发的。交易结构大概是这样的:
struct EIP7702Transaction {
uint8 txType; // 固定为0x4
address to; // EOA地址
uint256 gas; // Gas上限
uint256 gasPrice; // Gas价格
uint256 value; // 转账金额
bytes data; // 合约代码
uint256 nonce; // 交易序号
bytes signature; // 签名
}
EVM会拿Alice的签名和公钥比对,确认无误才继续。不然直接拒了,安全性第一。
签名对了,EVM从data字段里掏出要跑的代码。在Alice的例子中,这就是批量调用合约的代码,里面写了发ETH、转代币、送NFT的逻辑。这段代码是个字节码,待会儿会被临时关联到Alice的账户上。
EVM根据交易中的authorization字段(有委托合约地址),临时将指定的委托合约代码与Alice的EOA关联,让她的账户在执行时能跑这段代码,账户地址不变。
接下来,EVM开始跑代码,Alice的批量操作依次执行:
发1 ETH给Bob:
(bool success,) = BOB_ADDRESS.call{value: 1 ether}("");
require(success, "ETH transfer failed");
转100个MMT代币给Bob:
IERC20(MMT_TOKEN_ADDRESS).transfer(BOB_ADDRESS, 100 * 10**18);
送NFT给Frank:
IERC721(MYDOG_NFT_ADDRESS).transferFrom(msg.sender, FRANK_ADDRESS, 1);
以上是用Solidity写的示例逻辑,实际执行时会编译成字节码。
这三步是一个整体,要么全成功,要么全失败,原子性保证了操作的可靠性。
跑完后,EVM把Alice账户里的临时代码清掉,她又变回普通EOA。状态变化(ETH、代币、NFT的转移)会保留,但账户本身恢复原样。Alice这边,交易结束,Bob拿到了ETH和代币,Frank拿到了NFT,皆大欢喜。
EIP-7702是个挺酷的提案,它让EOA和合约账户的界限变模糊了,普通用户也能轻松玩转智能合约的功能。但我个人感觉也带来一个麻烦,后端监听合约事件变得复杂了。
通过新的0x4交易类型,EOA可以临时跑代码,支持批量交易、授权、Gas赞助、账户恢复等玩法。钱包负责构建交易,EVM负责执行和清理,整个流程既强大又灵活。
对于开发者来说,EIP-7702提供了一种新的账户抽象方式,能设计出更灵活、更用户友好的DApp。 随着Pectra升级上线,我觉得以后会有更多好玩的DApp冒出来。
你觉得怎么样?欢迎在评论区分享你的看法或疑问,也可以加我微信聊聊!
1. 验证交易签名
在 github.com/ethereum/go-ethereum/core/types/transaction.go 文件中,相关的代码包括:
func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error:这个函数用于检查签名的有效性,确保 v, r, s 值是合法的。func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error):这个方法返回一个带有给定签名的新交易。2. 加载委托代码
在 github.com/ethereum/go-ethereum/core/types/transaction.go 文件中,加载委托代码的相关代码包括:
func (tx *Transaction) Data() []byte:这个方法返回交易的输入数据。func (tx *Transaction) DecodeRLP(s *rlp.Stream) error 和 func (tx *Transaction) UnmarshalBinary(b []byte) error:这些方法用于解码交易数据,包括 EIP-2718 类型的交易数据。3. 创建临时合约账户
在 github.com/ethereum/go-ethereum/core/types/transaction.go 文件中,创建临时合约账户的相关代码包括:
func (tx *Transaction) Type() uint8 和 func (tx *Transaction) decodeTyped(b []byte) (TxData, error):这些方法用于识别和解码交易类型,其中 SetCodeTxType 对应 0x4 类型交易。func (tx *Transaction) setDecoded(inner TxData, size uint64):这个方法在解码后设置交易的内部数据。4. 执行临时合约逻辑
a. 在 github.com/ethereum/go-ethereum/core/vm/eips.go 文件中,有关执行临时合约逻辑的代码包括:
func enable7702(jt *JumpTable):这个函数应用 EIP-7702 的更改,支持委托设计器。opDelegateCall、opCallCode 等。b. 在 github.com/ethereum/go-ethereum/core/vm/operations_acl.go 文件中,有关执行临时合约逻辑的代码包括:
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc:这个函数创建了用于 EIP-7702 的动态 gas 计算函数。gasDelegateCallEIP7702、gasCallEIP7702 等。5. 清理临时合约并恢复状态
a. 在 github.com/ethereum/go-ethereum/core/types/transaction.go 文件中,清理临时合约并恢复状态的相关代码包括:
func (tx *Transaction) WithoutBlobTxSidecar() *Transaction 和 func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction:这些方法用于处理 blob 交易的 sidecar 数据,以确保交易状态的正确性。func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization 和 func (tx *Transaction) SetCodeAuthorities() []common.Address:这些方法返回和处理 0x4 类型交易的授权数据。b. 在 github.com/ethereum/go-ethereum/core/blockchain_test.go 文件中,有关清理临时合约并恢复状态的测试代码包括:
func TestEIP7702(t *testing.T):这个测试函数部署并调用了两个委托设计,并验证其执行的正确性。
我的产品体系: 《Web3开发转型陪跑计划》 《数字货币投资工具使用教学》 《AI编程教学》 本人技术栈: Solidity/Golang/Rust/NodeJS/Python/AI编程 项目合作,欢迎你来链接我! 都看到这里了,不关注一下嘛👇