本文深入探讨了以太坊交易的未来发展方向,包括信标链(Beacon Chain)、EIP-4844 Blob交易、EIP-7702 Set Code交易和EIP-712 Typed Structured Data Signing。
在之前的文章中,我们探讨了如今驱动以太坊的核心交易类型:Legacy, EIP-2930, 和 EIP-1559。如何创建以及它们实际上在链上的使用方式,而不仅仅是规范文档。
但以太坊不会停滞不前。
在这篇文章中,我们将深入探讨交易接下来的发展方向,以及它们如何变得更强大、更高效和更用户友好。我们将探讨三个重要的创新:
1. 以太坊的信标链(Beacon Chain)
2. EIP-4844 Blob 交易
3. EIP-7702 设置代码交易
4. EIP-712 类型化结构数据签名
如果你正在构建钱包、rollup 或依赖链下审批或高效 calldata 使用的协议,那么这些知识将使你的技术栈面向未来。
与往常一样,我们将使用真实示例和实时测试网数据,而不仅仅是理论。
让我们更深入地了解。
在我们深入研究 EIP-4844 Blob 交易 之前,我们需要了解什么是 信标链(Beacon Chain),它为什么存在,它为以太坊网络增加了什么,以及以太坊(1.0)到以太坊(2.0)的转变。
什么是信标链(Beacon Chain)
信标链(Beacon Chain)是以太坊从 工作量证明(proof-of-work, PoW) 过渡到 权益证明(proof-of-stake, PoS) 的第一部分(PoS 和 PoW 将在接下来的博客文章中介绍)。它于 2020 年启动,其主要目的是在隔离状态下测试和验证以太坊的 PoS 共识机制,而不处理真实的交易或应用状态。近两年来,它与以太坊的 PoW 链并行运行,生成 空块 仅用于测试和协调。
这种情况在 The Merge(合并) 期间发生了改变。
在 The Merge(合并)时,信标链接管了区块生产和共识的角色,不再存在两条不同的链。相反,只有一个权益证明的以太坊,现在每个节点需要两个不同的客户端。它开始接收来自 执行客户端(execution clients) 的执行负载(即实际的交易数据),并且 共识客户端(consensus clients) 使用其基于 PoS 的验证器网络将它们最终确定到区块中。同时,旧的 PoW 链禁用了其挖矿、共识和网络逻辑,有效地将所有控制权移交给信标链。
从那时起,以太坊变成了一个 单一的权益证明链,具有两个紧密连接的组件:
这两层可以使用引擎 API(Engine API)相互通信。
信标链(Beacon Chain)做什么
信标链 不 处理交易执行或智能合约逻辑,这仍然是 执行层 的责任。相反,它专注于共识职责,例如:
简而言之,虽然执行层是以太坊应用程序存在的地方,但信标链确保每个人都同意区块的顺序和有效性。
在短期和中期,甚至可能在长期内。Rollups(汇总)仍然是以太坊实现扩容的唯一无需信任的途径。由于 L1 gas 费用持续居高不下,整个生态系统越来越迫切地支持向Based Rollup 的使用方式进行广泛迁移。EIP-4844提供了一个至关重要的临时解决方案。它实现了完整分片将使用的相同交易格式,但实际上并没有对数据进行分片。相反,这些新的 携带 blob 的交易(blob-carrying transactions) 只是将数据添加到 信标链(beacon chain),在这里它被共识客户端下载并在短暂的保留窗口后自动丢弃。EIP-1559一样,正如我们在 之前的文章 中看到的那样。
然后有两个新字段:
max_fee_per_blob_gas
:这设置了你愿意为 blob 空间 支付的最高费用,类似于 max_fee_per_gas
如何用于常规 gas。blob_versioned_hashes
:这是指向实际 blob 数据的哈希列表。这些哈希是使用一种称为 kzg_to_versioned_hash
的密码学函数派生的,它们充当 blob 内容的承诺。(我们这里不会介绍)设置约束
为了构造或验证 EIP-4844 blob 交易,以太坊使用一种称为 KZG(Kate-Zaverucha-Goldberg)承诺 的密码学方案。该方案需要一个称为 可信设置(trusted setup) 的特殊工件——一个一次性预先计算的椭圆曲线幂列表,保存在代码的相同文件夹中。
发布后,你应该能够看到:
https://sepolia.etherscan.io/tx/0xfd044e8bccdba170a8afd3ec9248cb97fb4ebce49adbe392c47385c23ea82c3b
尽管围绕智能合约钱包进行了所有创新,但大多数用户仍然依赖 EOA(外部拥有帐户),即经典的以太坊密钥对。但 EOA 存在限制:它们无法批量处理操作,无法委派权限,也无法验证超出标准 ECDSA 曲线的签名。这限制了开发人员可以实现的 UX 改进类型。
EIP-7702 提出了一个强大但简单的修复方案:使 EOA 能够暂时像智能合约一样运行,使用一种新的交易类型。在这种交易中,用户可以仅在交易期间将自定义代码附加到他们的账户。没有部署,没有长期状态更改,也没有额外的 gas 成本。需要注意的是,这意味着 null 目的地无效。
它是如何工作的
在执行交易之前,EIP-7702 处理一个可选的授权元组列表,每个元组包含一个 chain_id
、address
、nonce
和 ECDSA 签名。每个元组都会被验证(包括签名恢复和 nonce 检查),如果有效,则授权帐户的代码将暂时替换为一个特殊的 委托指示器:0xef0100 || address
。这表明任何代码执行(例如,通过 CALL
、DELEGATECALL
或直接交易执行)都应重定向到指定 address
处的逻辑。值得注意的是,这种委托会影响执行,但不会影响代码检查:CODESIZE
和 CODECOPY
操作码反映了委托合约的代码,而委托帐户上的 EXTCODESIZE
仅返回委托标记的大小(23 字节)。即使交易回滚,委托仍然存在,并且来自同一帐户的多个元组将使用最后一个有效元组进行解析。
批量交易示例:
我们将部署两个合约 MultiDelegationInvoker
和 Invoked
(附加两个地址),并将两个不同的 EOA 指定为权限。每个权限将签署一个委托,授予 Invoked
的字节码在其身份下执行的权限。最后,一个 EIP-7702 交易将调用 MultiDelegationInvoker
,该交易将验证这些签名,注入委托的代码存根,并将执行路由到 Invoked
合约,所有这些都在一个原子调用中完成。
注意:我们将使用 Polygon Amoy 测试网
智能合约角度:
免费加入 Medium,以获取该作者的更新。
Invoked 智能合约:
pragma solidity ^0.8.24;
contract Invoked {
event Pinged(address sender);
function ping() external {
emit Pinged(msg.sender);
}
}
用于验证和授权的目标智能合约:
pragma solidity ^0.8.24;
contract MultiDelegationInvoker {
event PingSuccess(address from);
event PingStart(address from);
function triggerPings(address[] calldata froms) external {
emit PingStart(msg.sender);
for (uint i = 0; i < froms.length; i++) {
address from = froms[i];
bytes memory code = new bytes(23);
// Load the first 23 bytes of the code at `from`
// 加载 `from` 处代码的前 23 个字节
assembly {
extcodecopy(from, add(code, 0x20), 0, 23)
}
// Check if it starts with 0xef0100
// 检查它是否以 0xef0100 开头
if (
code.length == 23 &&
uint8(code[0]) == 0xef &&
uint8(code[1]) == 0x01 &&
uint8(code[2]) == 0x00
) {
// After verifying code starts with 0xef0100
// 验证代码以 0xef0100 开头后
address someModule;
assembly {
someModule := shr(96, mload(add(code, 0x23)))
}
(bool ok, ) = someModule.call(abi.encodeWithSignature("ping()"));
require(ok, "Ping failed");
emit PingSuccess(from);
} else {
revert("Not delegated or invalid delegation format");
}
}
}
}
让我们了解一下发生了什么:
for (uint i = 0; i < froms.length; i++) {
address from = froms[i];
…
}
froms
保存了你预先授权的 EOA。你将检查每个 EOA 的 “code” 是否有 23 字节的标记。
bytes memory code = new bytes(23);
assembly {
extcodecopy(from, add(code, 0x20), 0, 23)
}
new bytes(23)
为长度 ( 0x17
) 分配一个 32 字节的字,为数据分配另一个 32 字节的字。extcodecopy(from, …, 0, 23)
将 from
代码的前 23 个字节(你注入的存根!)拉入 code[0..22]
。(请记住 (0xef0100 || address)
被写入授权地址代码)
if (
code.length == 23 &&
uint8(code[0]) == 0xef &&
uint8(code[1]) == 0x01 &&
uint8(code[2]) == 0x00
) {
…
} else {
revert("Not delegated or invalid delegation format");
}
检查代码的前缀是否为 0xef0100
,如前所述。如果为真,则该地址已分配有代码。
address realModule;
assembly {
realModule := shr(96, mload(add(code, 0x23)))
}
在 extcodecopy
之后的 内存布局:
code[0..2]
= 标记字节code[3..22]
= 你签发的 20 字节 delegatee
地址add(code, 0x23)
= 指向 code[3]
的指针。
mload(...)
加载 32 字节(你的 20 字节地址 + 12 字节零填充)。
shr(96, …)
右移出填充,只留下 160 位地址。
(bool ok, ) = realModule.call(
abi.encodeWithSignature("ping()")
);
require(ok, "Ping failed");
emit PingSuccess(from);
在 realModule
上的 你的 模块合约上执行 ping()
。由于 EIP-7702 的委托调用语义,在该 ping()
中,msg.sender
仍然 是你委托的 EOA(而不是赞助者)。
钱包角度:
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"strings"
"time"
"transactiontypes/account"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
const (
// Public RPC URL for Polygon Amoy Testnet
// Polygon Amoy 测试网的公共 RPC URL
NodeRPCURL = "https://polygon-amoy.drpc.org"
AmoyChainID = 80002 // Polygon Amoy Testnet Chain ID
// Polygon Amoy 测试网链 ID
)
func main() {
ctx := context.Background()
client, err := ethclient.Dial(NodeRPCURL)
if err != nil {
log.Fatal("RPC connection failed:", err)
// RPC 连接失败:
}
// Load account
// 加载帐户
acc2Addr, acc2Priv := account.GetAccount(2)
acc1Addr, acc1Priv := account.GetAccount(1)
to := common.HexToAddress("0x87581c71b3693062f4d3e34617c3919ec1abf39b")
// Define contract and parameters
// 定义合约和参数
moduleAddr := common.HexToAddress("0x4f9c96915a9ce8cd5eb11a2c35ab587fc97d5126")
froms := []common.Address{
*acc1Addr,
*acc2Addr,
}
// Build calldata
// 构建 calldata
contractAbiJson := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "address","name": "from","type": "address"}],"name": "PingStart","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "address","name": "from","type": "address"}],"name": "PingSuccess","type": "event"},{"inputs": [{"internalType": "address[]","name": "froms","type": "address[]"}],"name": "triggerPings","outputs": [],"stateMutability": "nonpayable","type": "function"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(contractAbiJson))
data, err := parsedAbi.Pack("triggerPings", froms)
if err != nil {
log.Fatal("ABI pack error:", err)
// ABI 封装错误:
}
// Nonce and gas
// Nonce 和 gas
baseNonce2, err := client.PendingNonceAt(ctx, *acc2Addr)
if err != nil {
log.Fatal("Nonce fetch failed:", err)
// Nonce 获取失败:
}
nonce2 := baseNonce2 + 1
nonce1, err := client.PendingNonceAt(ctx, *acc1Addr)
if err != nil {
log.Fatal("Nonce fetch failed:", err)
// Nonce 获取失败:
}
gasTipCap, err := client.SuggestGasTipCap(ctx)
if err != nil {
log.Fatal("Failed to fetch gas tip cap:", err)
// 无法获取 gas tip cap:
}
baseFee, err := client.SuggestGasPrice(ctx)
if err != nil {
log.Fatal("Failed to fetch base fee:", err)
// 无法获取基础费用:
}
gasFeeCap := new(big.Int).Add(baseFee, gasTipCap)
// Create EIP-712-style signature for delegation
// 为委托创建 EIP-712 风格的签名
sig2, err := signEIP7702Delegation(acc2Priv, AmoyChainID, moduleAddr, nonce2)
if err != nil {
log.Fatal("Signature failed:", err)
// 签名失败:
}
sig1, err := signEIP7702Delegation(acc1Priv, AmoyChainID, moduleAddr, nonce1)
if err != nil {
log.Fatal("Signature failed:", err)
// 签名失败:
}
r2 := new(big.Int).SetBytes(sig2[:32])
s2 := new(big.Int).SetBytes(sig2[32:64])
v2 := uint8(sig2[64])
r1 := new(big.Int).SetBytes(sig1[:32])
s1 := new(big.Int).SetBytes(sig1[32:64])
v1 := uint8(sig1[64])
// Build EIP-7702 TxWithDelegation
// 构建带有委托的 EIP-7702 交易
delegation := types.SetCodeTx{
ChainID: uint256.NewInt(AmoyChainID),
Nonce: baseNonce2,
GasTipCap: uint256.MustFromBig(gasTipCap),
GasFeeCap: uint256.MustFromBig(gasFeeCap),
Gas: 120000,
To: to,
Data: data,
AuthList: []types.SetCodeAuthorization{
{
ChainID: *uint256.NewInt(AmoyChainID),
Address: moduleAddr,
Nonce: nonce1,
R: *uint256.MustFromBig(r1),
S: *uint256.MustFromBig(s1),
V: v1,
},
{
ChainID: *uint256.NewInt(AmoyChainID),
Address: moduleAddr,
Nonce: nonce2,
R: *uint256.MustFromBig(r2),
S: *uint256.MustFromBig(s2),
V: v2,
},
},
}
fullTx := types.NewTx(&delegation)
signedTx, err := types.SignTx(fullTx, types.LatestSignerForChainID(big.NewInt(AmoyChainID)), acc2Priv)
if err != nil {
log.Fatal("Signing failed:", err)
// 签名失败:
}
err = client.SendTransaction(ctx, signedTx)
if err != nil {
log.Fatal("Tx failed:", err)
// 交易失败:
}
fmt.Println("EIP-7702 Tx sent:", signedTx.Hash().Hex())
// EIP-7702 交易已发送:
time.Sleep(10 * time.Second)
receipt, err := client.TransactionReceipt(ctx, signedTx.Hash())
if err != nil {
fmt.Println("Waiting...")
// 等待...
} else {
fmt.Println("Tx mined in block", receipt.BlockNumber)
// 交易在区块中被挖掘
}
}
// signEIP7702Delegation creates a hash of (chainID, from, nonce) and signs it
// signEIP7702Delegation 创建 (chainID, from, nonce) 的哈希并对其进行签名
func signEIP7702Delegation(priv *ecdsa.PrivateKey, chainID int64, from common.Address, nonce uint64) ([]byte, error) {
// Encode [chain_id, address, nonce] in RLP
// 在 RLP 中编码 [chain_id, address, nonce]
msgPayload, err := rlp.EncodeToBytes([]interface{}{
big.NewInt(chainID),
from,
big.NewInt(int64(nonce)),
})
if err != nil {
return nil, err
}
// Prepend MAGIC 0x05
// 前置 MAGIC 0x05
prefixed := append([]byte{0x05}, msgPayload...)
// Hash
// 哈希
msgHash := crypto.Keccak256Hash(prefixed)
return crypto.Sign(msgHash.Bytes(), priv)
}
发生了什么:
连接并准备
acc1
, acc2
)(它们的地址和私钥)。设置目标合约
to
是你正在调用的智能合约地址。moduleAddr
是你将委托到的实现合约。构建 calldata
triggerPings(address[])
定义最小的 ABI。froms = [acc1, acc2]
) 打包到 data
中。获取 nonce 和 gas 参数
acc2
的待处理 nonce ( baseNonce2
) 用于交易,然后计算其后增量 nonce ( nonce2 = baseNonce2 + 1
) 用于委托条目。acc1
的待处理 nonce ( nonce1
) 用于其委托。gasFeeCap
。生成 EIP-7702 签名
signEIP7702Delegation()
签署元组 (chainID, moduleAddr, nonce)
:nonce1
nonce2
组装 SetCodeTx
&types.SetCodeTx{
ChainID: AmoyChainID,
Nonce: baseNonce2,
To: to,
Data: data,
Gas: 120_000, // some hardcoded fee to not write too much code
// 一些硬编码的费用,避免编写太多代码
AuthList: []SetCodeAuthorization{
{ Address: moduleAddr, Nonce: nonce1, R:…, S:…, V:… },
{ Address: moduleAddr, Nonce: nonce2, R:…, S:…, V:… },
},
}
签名和广播
SignTx
通过 acc2Priv 签署完整交易。client.SendTransaction
发送它。输出应如下所示:
https://amoy.polygonscan.com/tx/0x5d9b3a8ebbfc26ee94024f5e1f608e4fd1c933d9dcd78f8b4b127379015854d1
签署一个不透明的字节 blob 很容易,但在实际应用程序中,我们需要签署具有诸如 “amount”、“recipient” 或 “order ID” 等字段的丰富、结构化消息,而不仅仅是随机十六进制。如果你滚动自己的哈希,则很容易犯一个破坏安全性的错误。
EIP‑712 通过定义一种清晰、经过同行评审的方式来将你的数据结构转换为摘要,钱包可以以人类可读的形式显示该摘要,从而解决了这个问题。这意味着用户可以准确地看到他们正在签署的内容,合约可以在链上验证它,并且你可以通过在链下进行审批而不是在链上进行审批来节省 gas。
它是如何工作的:
扩展的签名宇宙
编码模式
"\x19\x01" ‖ domainSeparator ‖ hashStruct(message)
前导字节始终不同,因此每种情况都是明确。
类型化数据
struct Mail { address from; address to; string contents; }
uint8–256
、int8–256
、bytes1–32
、bool
、address
bytes
、string
Type[n]
/ Type[]
), 嵌套结构体hashStruct
typeHash
= keccak256(encodeType("Mail(address from,address to,string contents)"))
encodeData
= 将每个字段打包到 32 字节的槽中(首先对动态类型进行哈希处理)hashStruct(msg)
= keccak256(typeHash ‖ encodeData(msg))
域分隔符(Domain Separator)
EIP712Domain
结构体(例如 { name, version, chainId, verifyingContract }
),进行一次哈希处理,如下所示domainSeparator = hashStruct(eip712Domain);
签名和验证
eth_signTypedData_v4(name, types, domain, message)
→ 钱包显示清晰的字段并返回签名digest = keccak256("\x19\x01"‖domainSeparator‖hashStruct(message))
并使用 ecrecover
验证签名者。注意:有关更详细的规范,你可以在此处阅读
Permit
EIP-2612 “permit” 构建在 EIP-712 的类型化结构数据签名之上。它定义了一个 Permit(owner, spender, value, nonce, deadline)
结构体,钱包使用 EIP-712 域分隔符对其进行哈希处理,呈现给用户以供批准,并在链下进行签名。然后,Token的 permit(owner, spender, value, deadline, v, r, s)
函数通过 EIP-712 重新计算相同的摘要,并使用 ecrecover
验证持有者的签名和 nonce,并以原子方式设置 allowance[owner][spender] = value
。通过在底层利用 EIP-712,permit 实现了一种清晰、安全、gas 高效的 UX,而无需单独的链上 approve(...)
调用。
注意:想要支持这种批准的Token必须实现
permit
方法
示例:
在本例中,我们将编写一个智能合约,该合约通过其规范来验证 EIP-712,该规范已在前面解释过。
智能合约角度:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract PermitVerifier is EIP712 {
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
constructor() EIP712("MyDApp", "1") {}
function verifyPermit(
address owner,
address spender,
uint256 value,
uint256 nonce,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external view returns (bool) {
bytes32 structHash = keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonce,
deadline
)
);
bytes32 digest = _hashTypedDataV4(structHash);
return ECDSA.recover(digest, v, r, s) == owner;
}
}
钱包角度:
package main
const (
// Public RPC URL for Polygon Amoy Testnet
// Polygon Amoy 测试网的公共 RPC URL
NodeRPCURL = "https://polygon-amoy.drpc.org"
AmoyChainID = 80002 // Polygon Amoy Testnet Chain ID
// Polygon Amoy 测试网链 ID
)
func main() {
// 1) Connect to Amoy
// 1) 连接到 Amoy
client, err := ethclient.Dial("https://polygon-amoy.drpc.org")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// 2) Prepare the same EIP‑712 TypedData that the user signed
// 2) 准备用户签名的相同 EIP-712 类型化数据
acc2Addr, acc2Priv := account.GetAccount(2)
verifierAddr := common.HexToAddress("0xf80bb731f8ba49624dce8edb1a8188782287ff1e")
domain := apitypes.TypedDataDomain{
Name: "MyDApp",
Version: "1",
ChainId: math.NewHexOrDecimal256(AmoyChainID),
VerifyingContract: verifierAddr.Hex(),
}
types := apitypes.Types{
"EIP712Domain": {
{Name: "name", Type: "string"},
{Name: "version", Type: "string"},
{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"Permit": {
{Name: "owner", Type: "address"},
{Name: "spender", Type: "address"},
{Name: "value", Type: "uint256"},
{Name: "nonce", Type: "uint256"},
{Name: "deadline", Type: "uint256"},
},
}
deadline := big.NewInt(time.Now().Add(time.Hour).Unix())
nonce := big.NewInt(0)
message := apitypes.TypedDataMessage{
"owner": acc2Addr.Hex(),
"spender": acc2Addr.Hex(),
"value": "1000000000000000000",
"nonce": nonce.String(),
"deadline": deadline.String(),
}
typedData := apitypes.TypedData{
Types: types,
PrimaryType: "Permit",
Domain: domain,
Message: message,
}
// 3) Sign or supply your existing (v,r,s)
// 3) 签名或提供你现有的 (v,r,s)
domainSep, _ := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
msgHash, _ := typedData.HashStruct("Permit", typedData.Message)
digest := crypto.Keccak256(
[]byte("\x19\x01"),
domainSep,
msgHash,
)
sig, _ := crypto.Sign(digest, acc2Priv)
r := common.BytesToHash(sig[:32])
s := common.BytesToHash(sig[32:64])
v := uint8(```
// 6) 解码 bool 结果
out, err := parsed.Unpack("verifyPermit", res)
if err != nil {
log.Fatal(err)
}
fmt.Println("签名有效吗?", out[0].(bool))
if !out[0].(bool) {
log.Fatal("签名验证失败")
}
fmt.Printf("签名验证成功,所有者为: %s\n", acc2Addr.Hex())
}
你应该看到如下输出:
Signature verified successfully for owner: <你的地址>
在这个例子中,我们仅仅演示了一个 只读 的演示,直观地展示了 EIP-712 签名如何在链下进行验证。在真正的 permit 流程中,你不能仅停留在 verifyPermit
,你必须在链上实际调用 token 的 permit(...)
方法来:
allowance(owner, spender)
只有在 permit(...)
调用 之后 ,随后的 transferFrom(...)
才会成功。我们的例子展示了核心的签名和恢复逻辑,但是生产集成必须调用 tokenPermit.permit(...)
。
在阅读了这篇深入的文章之后,你将会理解以太坊是如何在简单的 ETH 转账之外演变的,为什么 rollups 和 blob-carrying txs 会让你以 L1 成本的一小部分扩展到每秒数百笔交易。Beacon Chain 如何为 PoS 共识提供动力,使其与 EVM 执行分离,以及像 EIP-7702 "SetCode" 这样的新交易类型如何为纯 EOAs 解锁原子批处理和赞助的 meta-txs,而 EIP-712 和 EIP-2612 "permit" 如何让你在链下签署丰富的批准,并在链上以单个、高 gas 效率的调用来使用它们。
资源:
- 原文链接: medium.com/@andrey_obruc...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!