ERC-5189: 通过认可操作实现账户抽象
一种账户抽象提案,避免了协议变更,同时保持与现有智能合约钱包的兼容性。
Authors | Agustín Aguilar (@agusx1211), Philippe Castonguay (@phabc), Michael Standen (@ScreamingHawk) |
---|---|
Created | 2022-06-29 |
Discussion Link | https://ethereum-magicians.org/t/erc-account-abstraction-via-endorsed-operations/9799 |
Table of Contents
摘要
本 ERC 提出了一种账户抽象 (AA) 的形式,它确保与现有智能合约钱包的兼容性,并为替代设计提供灵活性,同时避免对共识层进行更改。本提案没有为 AA 交易定义严格的结构,而是引入了 endorser
合约的概念。这些智能合约实例负责确定提交的 AA 交易的质量,从而安全地帮助打包者确定交易是否应保留在 mempool 中。 打算使其智能合约钱包与此 ERC 兼容的开发人员必须创建和部署一个 endorser
实例,或使用与其钱包兼容的现有实例。
动机
此账户抽象提案旨在实现一种通用的系统来执行 AA 交易,同时保持以下目标:
- 实现账户抽象的主要目标: 允许用户使用包含任意验证和执行逻辑的智能合约钱包,而不是 EOA 作为其主要账户。
- 去中心化:
- 允许任何打包者参与包含 AA 交易的过程。
- 处理所有通过公共 mempool 进行的活动,而无需将交易集中在中心化中继器上。
- 定义有助于维护健康 mempool 的结构,而不会使参与者面临无效或恶意负载的风险。
- 避免打包者、开发者和钱包之间的信任假设。
- 支持现有的智能合约钱包实现: 与所有已部署和激活的智能合约钱包一起使用,同时避免强制每个钱包实例手动升级。
- 提供一个不受限制的框架: 智能合约钱包在设计、限制和功能上彼此非常不同; 该提案旨在适应几乎所有可能的变体。
- 无开销: 与 EOA 替代方案相比,智能合约钱包已经具有成本开销,该提案不会使当前情况恶化。
- 支持其他用例:
规范
为了避免以太坊共识的改变,我们没有尝试为账户抽象交易创建新的交易类型。相反,AA 交易被打包在一个名为 Operation
的结构中,操作是由以下字段组成的结构:
字段 | 类型 | 描述 |
---|---|---|
entrypoint | address | 必须使用 callData 调用的合约地址,以执行 operation 。 |
callData | bytes | 必须传递给 entrypoint 调用的数据,以执行 operation 。 |
fixedGas | uint64 | 无论执行成本如何,操作都将支付的 gas 量,独立于 gasLimit 。 |
gasLimit | uint64 | 执行 operation 时必须传递的最小 gasLimit。 |
feeToken | address | 用于偿还打包者的代币的合约地址。 (address(0) 表示原生代币)。 |
endorser | address | 应该用于验证 operation 的 endorser 合约的地址。 |
endorserCallData | bytes | 调用 isOperationReady() 时必须传递给 endorser 的附加数据。 |
endorserGasLimit | uint64 | 验证 operation 时应传递给 endorser 的 gas 量。 |
maxFeePerGas | uint256 | operation 执行预计支付的最大 basefee 量。 (类似于 EIP-1559 max_fee_per_gas )。 |
priorityFeePerGas | uint256 | operation 执行预计支付给打包者的固定费用金额。 (类似于 EIP-1559 max_priority_fee_per_gas )。 |
feeScalingFactor | uint256 | 将计算出的费用转换为 feeToken 单位的缩放因子。 |
feeNormalizationFactor | uint256 | 将计算出的费用转换为 feeToken 单位的归一化因子。 |
hasUntrustedContext | bool | 如果为 true ,则操作 可能 具有不受信任的代码路径。 打包者应区别对待这些路径(请参阅不受信任的环境)。 |
chainId | uint256 | operation 打算执行的网络链 ID。 |
这些 Operation
对象可以发送到专用操作 mempool。 一类称为打包者的专门参与者(运行专用代码的区块生产者,或只是可以将交易中继到区块生产者的用户)侦听 mempool 上的操作并执行这些交易。
通过使用提供的 callData
调用 entrypoint
来执行交易。 entrypoint
可以是任何合约,但最常见的是钱包合约本身。 或者,它可以是部署钱包然后执行交易的中间实用程序。
Endorser 功能
Mempool 参与者需要能够从 “坏操作”(缺少付款或完全还原的操作)中过滤出 “好操作”(向打包者支付已定义费用的操作)。
此分类由 endorser
促进; endorser 必须是已部署的智能合约,它实现以下接口:
interface Endorser {
struct Operation {
address entrypoint;
bytes callData;
uint256 fixedGas;
uint256 gasLimit;
address endorser;
bytes endorserCallData;
uint256 endorserGasLimit;
uint256 maxFeePerGas;
uint256 priorityFeePerGas;
address feeToken;
uint256 feeScalingFactor;
uint256 feeNormalizationFactor;
bool hasUntrustedContext;
}
struct GlobalDependency {
bool baseFee;
bool blobBaseFee;
bool chainId;
bool coinBase;
bool difficulty;
bool gasLimit;
bool number;
bool timestamp;
bool txOrigin;
bool txGasPrice;
uint256 maxBlockNumber;
uint256 maxBlockTimestamp;
}
struct Constraint {
bytes32 slot;
bytes32 minValue;
bytes32 maxValue;
}
struct Dependency {
address addr;
bool balance;
bool code;
bool nonce;
bool allSlots;
bytes32[] slots;
Constraint[] constraints;
}
struct Replacement {
address oldAddr;
address newAddr;
SlotReplacement[] slots;
}
struct SlotReplacement {
bytes32 slot;
bytes32 value;
}
function simulationSettings(
Operation calldata _operation
) external view returns (
Replacement[] memory replacements
);
function isOperationReady(
Operation calldata _operation
) external returns (
bool readiness,
GlobalDependency memory globalDependency,
Dependency[] memory dependencies
);
}
Endorser 应该在 EndorserRegistry
中注册,并烧毁一定数量的 ETH。
要烧毁的 ETH 数量未在此提案中指定,因为 mempool 运营者可以自由设置自己的最低阈值。
Mempool 运营者可以接受来自 endorser 的没有烧毁任何 ETH 的操作,但他们会增加自身暴露于拒绝服务攻击的风险。
Mempool 运营者可以公布每个 endorser 所需的最小烧毁 ETH 数量。
为了检查操作状态,调用者必须首先调用 simulationSettings
以检索链上更改列表。
然后在调用 isOperationReady
方法时调用,endorser 必须返回以下信息:
- readiness: 当返回
true
时,表示交易必须正确执行,并且打包者必须支付提供的 gas 费用(即使操作的底层意图失败)。 - globalDependency: 可能不属于给定地址的依赖项的列表,定义交易的执行是否可能因这些全局变量中的一个的更改而失效。
maxBlockNumber
和maxBlockTimestamp
用作全局约束。 - dependencies: 必须监视的地址和存储槽的综合列表; 这些依赖项中的任何状态更改都必须触发对操作就绪状态的重新评估。
endorser 提供的信息有助于 mempool 运营者维护一个行为正确的 “良好” AA 交易池; 但它仅提供交易将正确执行的软保证。 在将交易包含在区块中之前,打包者必须始终模拟执行结果。
如果模拟结果失败,并且 endorser 仍然以相同的依赖项返回 readiness == true
,则 endorser 不可信任,并且必须被 mempool 运营者禁止。
依赖项列表是打包者了解哪些操作彼此完全独立的快捷方式。 此快捷方式对于以下方面很有用:(a)从 mempool 中清除不再有效的操作,以及(b)对于打包者了解哪些操作可以包含在同一区块中。
为了提高效率,可以使用 endorserCallData
向 endorser 提供其他信息。
如果使用,endorser 必须验证提供的 endorserCallData
是否有效且与其他提供的值相关。
虽然 endorser 部署在链上,但对 endorser 的调用不得在链上提交。 打包者必须读取 simulationSettings
的结果,执行链上更改并模拟链下执行。
全局依赖
字段 | 类型 | 描述 |
---|---|---|
baseFee | bool | 如果 block.basefee 应被视为依赖项,则为 true 。 |
blobBaseFee | bool | 如果 block.blockbasefee 应被视为依赖项,则为 true 。 |
chainId | bool | 如果 block.chainid 应被视为依赖项,则为 true 。 |
coinbase | bool | 如果 block.coinbase 应被视为依赖项,则为 true 。 |
difficulty | bool | 如果 block.difficulty 应被视为依赖项,则为 true 。 |
gasLimit | bool | 如果 block.gaslimit 应被视为依赖项,则为 true 。 |
number | bool | 如果 block.number 应被视为依赖项,则为 true 。 |
timestamp | bool | 如果 block.timestamp 应被视为依赖项,则为 true 。 |
txOrigin | bool | 如果 tx.origin 应被视为依赖项,则为 true 。 |
txGasPrice | bool | 如果 tx.gasprice 应被视为依赖项,则为 true 。 |
maxBlockNumber | uint256 | readiness 应用于的 block.number 的最大值。 |
maxBlockTimestamp | uint256 | readiness 应用于的 block.timestamp 的最大值。 |
endorser
必须使用 maxBlockNumber
和 maxBlockTimestamp
字段来限制 readiness
结果的有效性。 这对于仅在特定时间段内有效的操作很有用。
请注意,所有值都是包含的。 如果 endorser
确定 operation
的有效性是无限期的,则 maxBlockNumber
和 maxBlockTimestamp
字段必须设置为 type(uint256).max
。
依赖
字段 | 类型 | 描述 |
---|---|---|
addr | address | 依赖项条目的合约地址。 (每个地址仅允许一个条目)。 |
balance | bool | 如果 addr 的余额应被视为 operation 的依赖项,则为 true 。 |
code | bool | 如果 addr 的代码应被视为 operation 的依赖项,则为 true 。 |
nonce | bool | 如果 addr 的 nonce 应被视为 operation 的依赖项,则为 true 。 |
allSlots | bool | 如果 addr 的所有存储槽都应被视为 operation 的依赖项,则为 true 。 |
slots | bytes32[] | addr 的所有应被视为 operation 的依赖项的存储槽的列表。 |
constraints | Constraint[] | 具有特定值范围作为依赖项的 addr 的存储槽的列表。 |
endorser
不需要在依赖项列表中包含所有访问的存储槽,它只需要包含在更改后也可能导致就绪状态更改的存储槽。
请注意,allSlots
、constraints
和 slots
是互斥的。 如果 allSlots
设置为 true
,则 constraints
和 slots
必须为空数组。
如果在 constraints
中列出了一个槽,则不得在 slots
中列出该槽。
endorser
应尽可能使用 constraints
而不是 slots
,并使用 slots
而不是 allSlots
来限制打包者的重新评估要求。
例如,钱包可以使用存储为 WETH 的资金支付费用。 在
isOperationReady()
期间,endorser 合约可能会调用WETH
合约的balanceOf
方法来确定钱包是否有足够的WETH
余额。 即使正在访问 WETH 合约的 ETH 余额和 WETH 合约的代码,endorser 仅关心此操作的用户 WETH 余额,因此不会将它们作为依赖项包含在内。
约束
字段 | 类型 | 描述 |
---|---|---|
slot | bytes32 | 具有特定值范围作为依赖项的 addr 的存储槽。 |
minValue | bytes32 | readiness 应用于的 slot 的最小值(包含)。 |
maxValue | bytes32 | readiness 应用于的 slot 的最大值(包含)。 |
endorser
可以使用 minValue
和 maxValue
字段来限制 readiness
结果的有效性。 这允许 endorser 完全验证操作,即使此操作依赖于 endorser 无法直接访问的存储值。
请注意,所有值都是包含的。 当需要精确值时,minValue
和 maxValue
应设置为相同的值。
模拟设置
simulationSettings
方法返回一个替换列表,打包者应在模拟 isOperationReady
之前将这些替换应用于操作。 请注意,这些替换仅用于 isOperationReady
模拟,而不用于模拟操作本身。
字段 | 类型 | 描述 |
---|---|---|
oldAddr | address | 合约代码当前所在的链上地址。 |
newAddr | address | 执行模拟时合约代码应所在的地址。 |
slots.slot | bytes32 | 要更改的槽位置。 |
slots.value | bytes32 | 执行模拟之前要设置的槽的值。 |
endorser
可以使用 simulationSettings
方法来提供一个替换列表,打包者应在模拟 isOperationReady
之前将这些替换应用于网络。 这对于必须从特定合约地址调用的操作或依赖于特定存储值的操作很有用(例如 ERC-4337 的 EntryPoint)。
endorser
可以提供其自己的地址以进行替换。 在这种情况下,打包者应更新调用 isOperationReady
时使用的 endorser
地址。
行为不端检测
endorser
合约可能会以以下方式恶意或不规律地运行:
- (1) 它认为操作 “就绪”,但当执行操作时,它向打包者转移的费用少于约定的费用。
- (2) 它认为操作 “就绪”,但当执行操作时,顶级调用失败。
- (3) 在没有任何依赖项注册任何更改的情况下,它将就绪状态从
true
更改为false
。
打包者必须在任何 operation
的依赖项发生更改后放弃并重新评估就绪状态,这意味着只有被认为 ready
的操作才是构建下一个区块的候选者。
如果在模拟最终包含操作时,打包者发现它没有导致正确的付款(要么是因为交易失败,要么是因为转移的金额低于定义的费用),那么它必须禁止 endorser
。
当 endorser
被禁止时,mempool 运营者必须删除与 endorser 相关的所有 operations
。
不受信任的环境
在某些情况下,endorser
可能无法完全验证 operation
,但可能能够推断出给定的代码路径应该是安全的。 在这些情况下,endorser 可以将操作的某个部分标记为 untrusted
。 在此不受信任的上下文中访问的任何存储槽(余额、代码、nonce 或特定槽)都应自动被视为依赖项。
interface Endorser {
event UntrustedStarted();
event UntrustedEnded();
}
endorser 可以使用 UntrustedStarted
和 UntrustedEnded
事件来指示不受信任的上下文的开始和结束。 打包者应侦听这些事件并相应地扩展依赖项列表。
只有顶级 endorser
可以发出不受信任的上下文信号; 由不同合约发出的具有相同签名的任何其他事件都应被忽略。
不受信任的上下文可以多次打开和关闭,并且可以嵌套。 如果发出多个事件,打包者必须计算 UntrustedStarted
和 UntrustedEnded
事件的数量,并且只有当 UntrustedEnded
事件的数量等于 UntrustedStarted
事件的数量时,才将不受信任的上下文视为已结束。
如果 hasUntrustedContext
设置为 false
,则打包者应忽略任何 UntrustedStarted
和 UntrustedEnded
事件。
自动依赖关系图构建
必须监视在不受信任的上下文中执行的所有代码。 如果代码执行以下任何操作码,则必须相应地扩展依赖关系图。
操作码 | 依赖项 |
---|---|
BALANCE | dependencies[addr].balance = true |
ORIGIN | global.txOrigin = true |
CODESIZE | None |
CODECOPY | None |
GASPRICE | global.txGasPrice = true |
EXTCODESIZE | dependencies[addr].code = true |
EXTCODECOPY | dependencies[addr].code = true |
EXTCODEHASH | dependencies[addr].code = true |
COINBASE | global.coinbase = true |
TIMESTAMP | global.timestamp = true |
NUMBER | global.number = true |
DIFFICULTY | global.difficulty = true |
PREVRANDAO | global.difficulty = true |
CHAINID | global.chainId = true |
SELFBALANCE | dependencies[self].balance = true |
BASEFEE | global.baseFee = true |
SLOAD | dependencies[addr].slots[slot] = true |
CREATE | dependencies[addr].nonce = true |
CREATE2 | dependencies[contract].code = true |
请注意,不受信任的上下文会生成大量依赖项,并可能生成许多误报。 这可能会导致多次重新评估,从而导致操作从 mempool 中删除。 如果依赖项的数量超过某个阈值,打包者可以选择删除操作。
区块级依赖项特别敏感,因为它们将与大量操作共享。
建议仅在必要时使用不受信任的上下文,例如当 endorser
需要验证对不受其控制的钱包的嵌套签名时。
费用支付
endorser
必须保证该操作将至少偿还 tx.origin
花费的 gas。
付款始终以 feeToken
支付,feeToken
可以是任何代币标准(例如 ERC-20)。 如果 feeToken
是 address(0)
,则以本机货币支付。 当 feeToken
是 address(0)
时,feeScalingFactor
和 feeNormalizationFactor
必须等于 1
。
所有单位都以本机代币单位表示。 然后使用 feeScalingFactor
和 feeNormalizationFactor
将费用计算结果转换为 feeToken
单位。
gas 单位考虑了固定的 gas 量 (fixedGas
) 和可变的 gas 量 (gasLimit
)。 允许固定成本可以满足可能超出链接上执行范围的 gas 开销,例如 calldata 费用。 这也允许在执行成本低于预期时(例如,当内部调用在不恢复顶级交易的情况下失败时)减少偿还,同时仍然偿还打包者。
预期 gas 偿还的计算方式如下:
gasUnits = op.fixedGas + Min(gasUsed, op.gasLimit)
feePerGas = Min(op.maxFeePerGas, block.baseFee + op.priorityFeePerGas)
expectedRepayment = (gasUnits * feePerGas * op.feeScalingFactor) / op.feeNormalizationFactor
虽然 endorser
必须保证偿还 expectedRepayment
,但实际偿还金额可能会超过此费用。 例如,为了便于开发,打包者可以选择仅认可偿还操作提供的最大值的操作。
操作识别
可以通过操作哈希来标识操作,该哈希是 raw
文件的 CIDv1 多哈希,其中包含操作的规范 JSON 表示形式。 此哈希永远不会在链上使用,但它可用作指向可在系统之间共享的操作的唯一指针。
操作可以固定在 IPFS 网络上; 这将允许其他参与者在操作从 mempool 中删除后检索操作的内容。 此固定不是强制性的,如果需要操作的可见性,则可以由 mempool 运营者或钱包本身执行。
打包者收到操作时的行为
打包者可以添加自己的规则,以确保 AA 交易的成功中继并获得中继这些交易的报酬。 但是,我们在此处提出一个应该足够的基本规范。
当打包者收到 operation
时,它应该执行以下健全性检查:
endorserGasLimit
足够低 (<=MAX_ENDORSER_GAS
)。- endorser (i) 已注册并且有足够的 burn (>=
MIN_ENDORSER_BURN
),并且 (ii) 它尚未在内部标记为已禁止。 fixedGas
足够大,可以支付与提交交易相关的成本(即 calldata gas 成本)。gasLimit
至少是具有非零值的CALL
的成本。feeToken
是address(0)
或打包者愿意接受的已知代币地址。- 对于
address(0)
的feeToken
值或打包者愿意接受的值,feeScalingFactor
和feeNormalizationFactor
为1
。 maxFeePerGas
和priorityPerGas
高于打包者愿意接受的可配置最小值。- 如果 mempool 中存在具有完全相同依赖项集 AND 相同 endorser 地址的另一个操作,则新收到的操作的
maxFeePerGas
和priorityFeePerGas
必须比 mempool 上的操作高 12% 才能替换它。 (类似于具有相同 nonce 的 EOA 的工作方式)
然后,打包者应执行操作评估。
评估
为了评估 operation
,打包者必须在 endorser
上调用 simulationSettings()
以获取模拟设置值。 打包者必须应用这些设置并模拟对 endorser
的 isOperationReady()
调用。 如果 endorser 认为该操作已准备好,并且约束在范围内,则客户端必须将该操作添加到 mempool。 否则,必须删除该操作。
如果提供的任何依赖项的值发生更改,则 endorser
结果应无效,并且应重新评估其就绪状态。 如果操作就绪状态更改为 false
,则必须丢弃该操作。
在将操作包含在区块中之前,必须执行最后的模拟,这次是通过构建区块并探测结果。 必须模拟区块中列出的在操作之前的所有交易,并且必须查询 endorser
以了解就绪状态,以防某些依赖项发生更改。 然后必须重新评估约束以确保正确性。 最后,必须模拟操作。
如果在最终模拟期间操作失败,则必须禁止 endorser
,因为 (i) 它返回了错误的就绪状态或 (ii) 它独立于依赖项更改了操作就绪状态。
可选规则
Mempool 客户端可以实施其他规则,以进一步防止恶意构造的交易。
- 将接受的依赖项的大小限制为
MAX_OPERATION_DEPENDENCIES
,删除跨越边界的操作。 - 将操作可能触发重新评估的次数限制为
MAX_OPERATION_REEVALS
,删除跨越边界的操作。 - 限制 mempool 中依赖于相同依赖项槽的操作数量。
如果这些规则被广泛采用,钱包开发者应尽可能减少对依赖项的使用,并避免频繁更新的共享依赖项槽。
操作包含后
没有定义操作只能执行一次的限制。
打包者不应在成功将操作包含在区块中后删除该 operation
,打包者可以执行评估。
如果 endorser
仍然返回 readiness == true
(包含后),则该操作应被视为任何其他健康操作,因此可以将其保留在 mempool 中。
Endorser 注册表
endorser 注册表用作注册每个 endorser 的 burn 的位置,任何人都可以通过调用 addBurn()
函数来增加任何 endorser 的 burn。
所有 burn 实际上都是永久锁定的; 如果没有协议更改,则无法在链上可靠地证明削减,因此它仍然是一个虚拟事件,mempool 运营者将忽略存入的 ETH。
实现
(示例)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
contract EndorserRegistry {
event Burned(
address indexed _endorser,
address indexed _sender,
uint256 _new,
uint256 _total
);
mapping(address => uint256) public burn;
function addBurn(address _endorser) external payable returns (uint256) {
uint256 total = burn[_endorser] + msg.value;
burn[_endorser] = total;
emit Burned(_endorser, msg.sender, msg.value, total);
return total;
}
}
理由
Griefing 保护
纯粹基于智能合约钱包的账户抽象系统的主要挑战是 DoS 安全性:包含操作的打包者如何确保在不执行整个操作的情况下获得报酬?
打包者可以执行整个操作以确定其是否健康,但出于以下原因,此操作可能既昂贵又复杂:
- 打包者没有办法以减少的 gas 量模拟交易; 它必须使用整个
gasLimit
,从而使自身面临更高程度的 griefing。 - 打包者没有办法知道状态的更改是否会影响操作,因此它必须在每次更改后重新评估操作。
- 打包者没有办法知道状态的更改是否会使 mempool 的很大一部分无效。
在此提案中,我们添加了 endorser
作为打包者以受控方式验证任意操作的工具,而无需打包者了解此类操作的任何内部工作原理。
实际上,我们将责任从钱包转移到钱包开发者; 开发者必须编码、部署和烧毁 ETH 以用于 endorser
; 这是一种近乎理想的方案,因为开发者知道他们的钱包操作是如何工作的,因此他们可以构建工具来有效地评估这些操作。
此外,该规范尽可能简单,因为强制执行高度结构化的智能合约钱包交易行为和架构可能会阻碍更多创新类型的钱包的采用以及它们之间共享标准的采用。
烧毁的 ETH
任何人都可以部署 endorser 合约,钱包客户端是提供给定交易应使用的 endorser 合约的人。 打包者不必依赖于他们需要维护的链下注册表,而是可以调用 endorser 注册表来查看请求的 endorser 合约是否存在以及为其烧毁了多少 ETH。 然后,打包者可以决定接受一个 endorser 合约需要多少 ETH 烧毁的最低阈值。 打包者也可以自由地支持不属于注册表的 endorser 合约,或者属于注册表但没有与其关联的 ETH 烧毁的 endorser 合约。
最小的开销
由于 AA 交易的验证是由打包者在链下而不是在执行时完成的,因此执行交易没有额外的 gas 费用开销。 打包者承担风险,而不是所有用户都必须为该安全性付费。
与替代提案的区别
- 此提案不需要监视禁止的操作码或存储访问边界。 钱包在验证和执行期间可以完全自由地使用任何 EVM 功能。
- 此提案未指定任何重放保护逻辑,因为所有现有的智能合约钱包都已经有自己的重放保护逻辑,并且它们之间的设计可能会有所不同。 Nonce 可以使用
dependency
传达给打包者。 - 此提案未指定任何预部署逻辑,因为它可以通过 entrypoint 直接处理。
- 此提案不需要钱包接受来自受信任的 entrypoint 合约的
execution
交易,从而减少了开销并允许现有钱包与该提案兼容。 - 本提案不区分
execution
和signature
payload,这种区别仍然是特定于实现的。
向后兼容性
此 ERC 不会更改共识层,也不会对现有的智能合约钱包施加更改,因此不存在向后兼容性问题。
安全注意事项
此 ERC 不会对链上交互进行更改。 Endorser 明确用于链下验证。
打包者负责管理自己的安全并确保他们获得包含在区块中的交易的报酬。
版权
在 CC0 下放弃版权及相关权利。
Citation
Please cite this document as:
Agustín Aguilar (@agusx1211), Philippe Castonguay (@phabc), Michael Standen (@ScreamingHawk), "ERC-5189: 通过认可操作实现账户抽象 [DRAFT]," Ethereum Improvement Proposals, no. 5189, June 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5189.