- ERC-4437 là gì?
- User operations
- Ai sẽ là người call smart contract wallet?
- Goal: no separate EOA
- No separate EOA recap
- Bundling
- Tham khảo
ERC-4337 sử dụng khái niệm
account
, về bản chất nó là một smart contract, nên trong bài này đôi khi ta sử dụng các từ khóasmart contract account
,smart contract wallet
,wallet
ta hiểu ý nghĩa của chúng là tương đương.
ERC-4437 là gì?
ERC-4337 được đề xuất bởi EIP-4337 (Account Abstraction via Entry Point Contract specification), là một đề xuất sử dụng EntryPoint
contract để đạt được account abstraction mà không làm thay đổi consensus layer protocol của Ethereum.
Thay vì phải thay đổi logic của consensus layer, ERC-4337 mô phỏng lại cách hoạt động của hệ thống transaction mempool nhưng ở một higher-level. User sẽ gửi các UserOperation
đóng gói transaction data kèm chữ kí của user. Miner hoặc bundler sử dụng các dịch vụ như Flashbot có thể đóng gói nhiều UserOperation
thành một bundle transaction
và đưa vào block như một transaction thông thường.
ERC-4337 cũng giới thiệu một cơ chế paymaster
cho phép đa dạng hóa việc trả phí:
- user có thể trả phí sử dụng ERC-20 token thay vì ETH
- cho phép một bên thứ 3 có thể trả phí hộ user, gọi là gas sponsor
Hiện nay ERC-4337 vẫn đang trong giai đoạn draft và chưa hoàn thiện hoàn toàn. Tuy nhiên vì ERC-4337 không làm thay đổi Ethereum protocol, nên rất nhiều implement đã được thực hiện và đưa ra thị trường, ví dụ:
- Biconomy
- Safe
- Ambire Wallet
- Soul Wallet
- Candide
- BSL Wallet
- Infinitism
- Stackup
- Etherspot
- Kernel (ZeroDev)
- Forum Wallet
- Kriptonio
- TrueWallet
- Patch Wallet
- Openfort
Trong bài này ta sẽ đi vào tìm hiểu cách mà ERC-4337 implement account abstraction, nhưng thay vì trực tiếp nhảy vào đề xuất gốc của ERC-4337 sẽ rất khó hiểu, ta sẽ đi theo hướng bottom-up từ bài toán nhỏ nhất, và phát triển dần cho tới khi đạt được kiến trúc hoàn thiện của ERC-4337.
Ok, let’s get started!
User operations
Ta biết rằng với Account Abstraction, account của chúng ta là một smart contract wallet, có khả năng lưu trữ và thao tác với asset trong đó. Ta sẽ viết một contract có một hàm executeOp
nhận đầu vào là một UserOperation
như sau:
contract Wallet {
function executeOp(UserOperation op);
}
trong đó UserOperation
sẽ bao gồm các trường giống như khi ta gửi transaction thông thường bằng eth_sendTransaction
:
struct UserOperation {
address to;
bytes data;
uint256 value; // Amount of wei sent
uint256 gas;
// ...
}
ta sẽ cần thêm thông tin để xác thực xem transaction này có được gửi từ đúng người hay không? nếu đúng thì ta mới thực hiện nó và revert trong trường hợp ngược lại. Trong hầu hết các trường hợp, ta sẽ thêm vào chữ ký của người gửi và nonce để tránh replaying attack
.
struct UserOperation {
// ...
bytes signature;
uint256 nonce;
}
Dù về mặt lý thuyết wallet có thể thực hiện bất cứ verification logic nào mà nó muốn với
signature
vànonce
, tuy nhiên về mặt practice thì wallet nên yêu cầu signature là chữ ký với toàn bộ những trường còn lại trong op, điều này tránh được việc một bên nào đó bắt được gói tin và thay đổi hoặc làm giả các trường dữ liệu trong op. Tương tự vậy, wallet cũng nên reject tất cả những op với số nonce đã được sử dụng.
Ai sẽ là người call smart contract wallet?
Vì op hoàn toàn nằm trong kiểm soát của op owner về dữ liệu và chữ ký, nên hàm executeOp(op)
có thể được gọi bởi bất kì ai mà không hề có rủi ro nào về bảo mật.
Trong Ethereum, tất cả các transaction đều phải bắt nguồn từ một EOA, và EOA sẽ trả phí giao dịch bằng ETH, nên lúc này ta có thể thêm vào hệ thống một EOA riêng để gọi hàm của smart contract wallet. EOA này sẽ chỉ cần hold một lượng ETH vừa đủ để trả phí giao dịch mà thôi, còn mọi các tài sản giá trị khác sẽ vẫn nằm trong smart contract wallet.
Bức tranh về account abstraction với smart contract wallet hiện tại đang trông thế này:
Goal: no separate EOA
Một điểm yếu của phương án trên là ta sẽ luôn cần phải có một EOA bên ngoài để gọi hàm từ wallet. Sẽ thế nào nếu ta không muốn duy trì một account ngoài như vậy? Hiện tại, ta vẫn muốn trực tiếp trả phí gas bằng ETH. Ta chỉ đơn giản không muốn phải sử dụng thêm một account khác mà thôi.
Như đã nói executeOp(op)
có thể được gọi bởi bất kì ai, do đó ta hoàn toàn có thể nhờ ai đó sử dụng EOA của họ để gọi hàm. Người này sẽ được gọi là executor
Tất nhiên không ai muốn làm việc không công cả, vì executor phải trả phí giao dịch, nên phương án ở đây là ta sẽ hold một ít ETH ở smart contract wallet, và tiến hành hoàn trả phí gas cho executor mỗi khi họ gọi hàm executeOp(op)
giúp mình.
“Executor” không phải là một khái niệm trong ERC-4337, nhưng ở hiện tại nó mô tả chính xác vai trò của nó trong hệ thống. Về sau, ta sẽ thay thế nó bằng khái niệm thực tế được sử dụng trong ERC-4337 là “bundler”, nó không có ý nghĩa gì lắm ở lúc này vì ta đang không tiến hành đóng gói cái gì cả. Ở một số protocol khác cũng có thể gọi nó là “relayer”.
Thử nghiệm đầu tiên: Wallet sẽ refund cho executor khi xong transaction
Quay trở lại với interface của wallet hiện tại:
contract Wallet {
function executeOp(UserOperation op);
}
tại cuối của hàm executeOp
, ta sẽ tính toán xem nó sử dụng hết bao nhiêu gas để refund lượng ETH phù hợp lại cho executor.
Với một trusted wallet, thì thiết kế này hoàn toàn ổn. Nhưng executor cần biết chắc chắn rằng wallet sẽ thực sự refund phí giao dịch họ đã trả. Nếu như executor thực hiện xong executeOp
mà wallet không thực sự refund tiền thì rõ ràng là excutor công cốc.
Để tránh kịch bản xấu này, executor có thể tiến hành simulate executeOp
tại local, ví dụ sử dụng debug_traceCall và xem nó có thực sự refund phí giao dịch hay không? Nếu có thì mới thực hiện executeOp
thực sự.
Nhưng có một vấn đề ở đây là simulation sẽ không hoàn toàn thể hiện được 100% transaction trong thực tế. Cũng có nghĩa là nó có thể success ở simulation, nhưng fail ở execution. Một wallet độc hại có thể làm điều này, để transaction được thực hiện free mà không phải trả một khoản phí nào cả.
Lý do mà transaction có thể khác giữa simulation và execution thực tế có thể kể đến như:
- Operation đọc dữ liệu từ storage, nhưng dữ liệu storage lúc simulation có thể khác với dữ liệu storage lúc thực thi thực tế.
- Operation có thể sử dụng những opcodes như
TIMESTAMP
,BLOCKHASH
,BASEFEE
, etc. Các thông tin này liên tục thay đổi từ block này qua block khác, dẫn đến sự sai khác giữa lúc simulation và execution.
Ta có thể nghĩ đến một giải pháp tình thế, đấy là giới hạn các opcodes được phép sử dụng trong UserOperation
, và reject những opcodes như kể trên. Tuy nhiên điều này không tốt lắm, vì việc giới hạn các opcodes có thể dẫn tới việc rất nhiều những transaction hợp lệ cũng bị giới hạn theo, một trong số đó có thể kể đến như việc tương tác với Uniswap sử dụng TIMESTAMP
opcode.
Vì executeOp
có thể thực hiện bất kì logic code nào, nên thật khó có thể giới hạn để tránh việc nó thực hiện bypass simulation. Vấn đề này dường như bất khả thi với interface wallet hiện tại.
Giải pháp tốt hơn: giới thiệu EntryPoint
Vấn đề ở đây là ta đang đòi hỏi executor thực hiện code từ một untrusted contract, việc này rõ ràng đẩy rủi ro về cho executor. Cái mà executor muốn là chạy code với một môi trường được đảm bảo. Điều này có thể đạt được bằng cách sử dụng smart contract, nên ở đây ta sẽ thêm vào một trusted contract mới gọi là EntryPoint
, nó sẽ cung cấp một phương thức cho executor gọi:
contract EntryPoint {
function handleOp(UserOperation op);
// ...
}
handleOp
sẽ thực hiện các công việc sau:
- Kiểm trả xem wallet có đủ fund để trả cho lượng gas max mà transaction có thể sử dụng hay không (dựa trên trường
gas
trong op). Nếu không, reject. - Gọi hàm
executeOp
của wallet với lượng gas phù hợp, đồng thời theo dõi xem hết bao nhiêu gas. - Gửi lại ETH phí gas giao dịch cho executor.
Với công việc cuối cùng, ta cần Entry Point phải hold một lượng ETH nào đấy để nó có thể gửi lại phí giao dịch cho executor thay vì wallet, vì ta không luôn chắc chắn rằng có thể lấy lại phí giao dịch từ wallet. Cũng theo đó thì Entry Point sẽ cần một phương thức để wallet hoặc một bên thứ 3 nào đó nạp ETH vào để trả phí gas cho nó; đồng thời cần một phương thức để lấy lại ETH khi cần.
contract EntryPoint {
// ...
function deposit(address wallet) payable;
function withdrawTo(address payable destination);
}
Với phương án này, executor sẽ luôn đảm bảo được trả lại phí giao dịch từ trusted contract EntryPoint.
Đây là một bước cải tiến lớn cho executor, nhưng lại nảy sinh vấn đề lớn khác cho wallet…
Chẳng phải chúng ta cũng có thể trả phí gas sử dụng ETH từ chính wallet, thay vì phải deposit vào trong Entry Point hay sao? Đúng ta có thể, nhưng ta sẽ cần vài thay đổi để thực hiện nó, ta sẽ giới thiệu ở phần sau đây, và kể cả khi làm như vậy thì ta cũng vẫn cần có hệ thống deposit/withdraw. Thêm nữa, hệ thống deposit/withdraw cũng cần thiết để hỗ trợ cho paymaster.
Tách biệt giữa validation và execution
Interface của ta đang như sau:
contract Wallet {
function executeOp(UserOperation op);
}
Trên thực tế hiện tại executeOp
đang làm 2 nhiệm vụ:
- validate xem op có được xác thực hay không?
- thực thi op
Nếu wallet owner là người thực hiện và trả phí, sẽ không hề có vấn đề gì. Nhưng hiện giờ người thực hiện lại là executor
, nó là điều khác biệt rất lớn.
Với thiết kế hiện tại, wallet sẽ phải refund phí giao dịch cho executor trong bất cứ trường hợp nào. Nhưng rõ ràng ta không hề muốn wallet phải trả tiền nếu validation bị fail. Nếu validation fail, điều đó có nghĩa là user operation đã có vấn đề, hoặc nó bị làm sai, hoặc nó bị làm giả, hoặc vì bất cứ lý do gì khác, rõ ràng wallet sẽ không muốn trả phí cho giao dịch này.
Trong trường hợp này, hàm executeOp
có thể block operation lại, nhưng dù thực hiện tới đâu thì nó vẫn cứ phải trả phí gas tới đó. Đó là một vấn đề, bởi vì kẻ xấu có thể lợi dụng nó để gửi hàng tá những operations không hợp lệ đến wallet để khiến wallet cạn sạch tiền.
Ngược lại, nếu validation thành công, nhưng execution fail sau đó, thì wallet lúc này cần phải trả phí gas. Điều này cũng giống như EOA gửi một transaction bình thường nhưng sau đó nó bị revert
vậy, nó đã pass validation có nghĩa là wallet owner đã đồng ý thực hiện transaction, và sẵn sàng trả phí gas.
Interface hiện tại của wallet không đảm bảo được việc tách biệt giữa validation fail và execution fail, nên ta sẽ tách biệt 2 việc này ra:
contract Wallet {
function validateOp(UserOperation op);
function executeOp(UserOperation op);
}
Với việc tách ra 2 hàm này, thì handleOp
của Entry Point lúc này sẽ làm những việc sau:
- Call
validateOp
. Nếu nó fail, stop. - Khoanh lại lượng deposit ETH của wallet đủ để trả phần max gas có thể sử dụng (dựa trên trường
gas
trong op). Nếu không đủ, reject. - Gọi
executeOp
và theo dõi xem sử dụng hết bao nhiêu gas. Tùy theo việc thực hiện success hay fail, refund cho executor lượng gas ta đã khoanh lại và trả phần còn lại cho deposit của wallet.
Với sự cải tiến này, wallet sẽ chỉ phải trả phí cho những operation mà nó đồng ý mà thôi.
Ta cần chắc chắn rằng một người lạ bất kì không thể trực tiếp gọi hàm
executeOp
để khiến cho op được thực hiện mà không qua validation. Việc này có thể được giải quyết đơn giản bằng việc giới hạnexecuteOp
chỉ được phép gọi bởi Entry Point mà thôi. Nếu một ví độc hại thực hiện luôn việc execution trongvalidateOp
thì sao? bởi khi này nếu execution fail thì nó sẽ không phải trả phí gas nữa. Ở phần tiếp theo ta sẽ giới thiệu một vài những giới hạn để việc này trên thực tế trở nên hầu như bất khả thi.
Simulation redux
Vấn đề lại tiếp tục nảy sinh với executor
.
Giờ đây khi một unauthorized user submit operation lên wallet, operation này rõ ràng sẽ bị fail khi thực hiện validateOp
và wallet sẽ không phải trả phí gas. Tuy nhiên executor vẫn sẽ phải trả phí gas cho việc thực hiện validateOp
on-chain, và không nhận được bồi thường nào. Kẻ xấu có thể lợi dụng điều này để làm cho executor liên tục thực hiện fail validateOp
, và cạn sạch tiền.
Ở phần trước ta đã có nói đến giải pháp simulate transaction dưới local để xem nó có success hay không, và chỉ khi pass simulation, transaction mới được submit lên bằng cách gọi handleOp
on-chain. Tuy nhiên khi đó ta đã gặp vấn đề là executor không thể giới hạn quá nhiều những opcodes để tránh trường hợp success tại simulation nhưng fail tại execution.
Nhưng lần này có một chút khác biệt.
Executor không cần thiết phải simulate toàn bộ execution mà trước đó bao gồm cả validateOp
và executeOp
, mà giờ đây chỉ cần simulate phần validateOp
để biết nó có thể được trả tiền hay không mà thôi. Không giống như executeOp
phải thực hiện bất kì action nào của wallet khi tương tác với blockchain, ta có thể giới hạn rất chặt chẽ với validateOp
.
Cụ thể, executor sẽ reject op trừ phi validateOp
thỏa mãn những giới hạn sau đây:
- Không bao giờ sử dụng những
OPCODES
trong banlist, ví dụTIMESTAMP
,BLOCKHASH
, etc. - Chỉ truy cập những storage thuộc wallet associated storage - được định nghĩa như sau:
- Storage của chính wallet
- Storage của contract khác tại slot được định nghĩa trong wallet tại
mapping(address => value)
. - Storage của contract khác tại storage slot bằng với địa chỉ của contract (đây là dạng storage thông thường không xuất hiện trong Solidity)
Mục đích của các giới hạn này là để hạn chế thấp nhất việc validateOp
success tại simulation nhưng lại fail tại execution.
Việc không sử dụng OPCODE
trong banlist là hiển nhiên dễ hiểu, nhưng phần giới hạn về storage thì có vẻ khá khó hiểu. Ta biết rằng storage có thể thay đổi theo thời gian, gây ra việc success lúc simulation nhưng fail lúc execution, nên ý tưởng của việc giới hạn truy cập storage vào trong chỉ những slot xác định như trên, là làm cho cost của việc bypass simulation (bằng cách kẻ xấu phải thay đổi associated storage giữa lúc simulation và execution) trở nên khó khăn hơn rất nhiều, đến mức không đáng để bỏ công sức ra thực hiện.
Với các giới hạn này, lúc này executor và wallet đều đã được an toàn.
Một lợi ích nữa của storage restriction, là khi ta gọi
validateOp
từ nhiều wallet khác nhau sẽ hiếm khi bị ảnh hưởng lẫn nhau vì chúng chỉ có thể truy cập vào những storage giới hạn. Điều này vô cùng quan trọng khi ta nói về việc đóng gói các op sau này.
Cải tiến tiếp theo: trả phí gas trực tiếp từ wallet
Hiện tại trước khi thực hiện user operation, wallet cần phải thực hiện deposit ETH vào trong Entry Point. Nhưng EOA thông thường chẳng phải vẫn tự trả phí gas hay sao? tại sao ta lại không làm giống như vậy với smart contract account?
Ta có thể làm được như vậy, vì giờ đây ta đã tách biệt rõ ràng validation và execution, Entry Point có thể yêu cầu wallet gửi một lượng ETH cho phần op validation, hoặc op sẽ bị reject.
Ta sẽ update hàm validateOp
để Entry Point có thể yêu cầu fund và có quyền reject nếu validateOp
không trả lượng ETH như đã yêu cầu.
contract Wallet {
function validateOp(UserOperation op, uint256 requiredPayment);
function executeOp(UserOperation op);
}
Vì tại validation time thì ta sẽ không thể biết được chính xác lượng gas sẽ được sử dụng tại execution, nên Entry Point sẽ yêu cầu lượng gas max dựa trên trường gas
bên trong user operation. Tại cuối giao dịch, ta sẽ muốn trả lại lượng gas không sử dụng hết cho wallet.
Có một điểm đáng lưu ý ở đây, khi viết smart contract, ta không luôn chắc chắn có thể gửi ETH một cách an toàn tới một smart contract. Vì nó có thể kích hoạt những code độc hại như re-entrancy, hay gây fail transaction, hay contract từ chối nhận ETH, etc… Do đó ta sẽ không trực tiếp gửi ETH lại cho wallet.
Thay vì thế, ta vẫn hold nó trong Entry Point và cho phép smart contract có thể rút tiền về bất cứ khi nào cần. Đây chính là pull-payment pattern
.
Do đó chính xác những gì chúng ta sẽ làm là gửi lượng gas cho validateOp
bằng deposit
và cho phép wallet có thể rút tiền về thông qua withdrawTo
. Ta thấy rằng dù giờ đây wallet trả tiền phí giao dịch như EOA, nhưng ta vẫn cần deposit/withdrawTo
trong EntryPoint.
Điều này có nghĩa là wallet gas payment sẽ thực tế đến từ 2 nơi: ETH mà wallet đã deposit vào trong Entry Point, và ETH trong chính wallet.
Entry Point sẽ cố gắng trả phí gas bằng ETH đã được deposit từ trước, và sau đó nếu không đủ nó sẽ yêu cầu phần còn lại khi gọi validateOp
.
Executor incentives
Cho đến lúc này, executor vẫn đang làm việc không công, mà còn đối mặt với rất nhiều rủi ro bị quịt tiền phí giao dịch. Ai sẽ sẵn sàng làm công việc này chứ?
Đây là ta cần một hệ thống lợi ích cho executor, bằng cách cho phép wallet owner có thể submit thêm tip
cho executor trong user operation:
struct UserOperation {
// ...
uint256 maxPriorityFeePerGas;
}
giống như cái tên, maxPriorityFeePerGas
là lượng gas mà user sẵn sàng trả để transaction của mình được ưu tiên.
Khi này, executor khi gửi op cho EntryPoint trong handleOp
có thể lựa chọn một giá trị maxPriorityFeePerGas
nhỏ hơn và đút túi phần chênh lệch.
Entry Point as a Singleton
Chúng ta đã nói về vai trò và cách mà Entry Point hoạt động. Ta có thể chú ý rằng Entry Point được thiết kế tách biệt hoàn toàn không phụ thuộc vào bất cứ wallet hay executor cụ thể nào. Do đó Entry Point hoàn toàn có thể là một singleton xuyên suốt toàn bộ hệ sinh thái. Tất cả mọi wallet và mọi executor đều tương tác với cùng một contract Entry Point duy nhất mà thôi.
Nó có nghĩa là trong user operation, ta cần chỉ rõ địa chỉ wallet mà ta muốn tương tác, để khi tương tác với Entry Point thông qua handleOp
, Entry Point biết địa chỉ wallet đến để tiến hành validation và execution.
struct UserOperation {
// ...
address sender;
}
No separate EOA recap
Mục tiêu của chúng ta là tạo ra một on-chain smart contract wallet có khả năng trả phí gas cho giao dịch mà không cần wallet owner phải duy trì thêm một EOA riêng rẽ, và ta đã đạt được mục tiêu!
Wallet interface của chúng ta lúc này như sau:
contract Wallet {
function validateOp(UserOperation op, uint256 requiredPayment);
function executeOp(UserOperation op);
}
toàn bộ hệ thống có một EntryPoint có interface như sau:
contract EntryPoint {
function handleOp(UserOperation op);
function deposit(address wallet) payable;
function withdrawTo(address destination);
}
Khi wallet owner muốn thực hiện một action, họ sẽ tạo ra một User Operation, off-chain, nhờ một executor thực thi nó giùm mình.
Executor sẽ tiến hành simulate hàm validateOp
trong wallet để quyết định xem có nhận user op này hay không. Nếu đồng ý, executor sẽ gửi transaction đến Entry Point bằng cách gọi handleOp
.
Entry Point sẽ handle việc validation và execution on-chain, sau đó sẽ refund ETH về cho executor từ deposit fund của wallet trong Entry Point.
Bundling
Với implementation hiện tại, mỗi executor sẽ gửi một transaction để thực hiện một user op. Nhưng giờ đây ta đã có một Entry Point hoàn toàn không phụ thuộc vào một wallet cụ thể nào cả, do đó ta hoàn toàn có thể tiết kiệm được gas bằng cách tập hợp nhiều những user op từ những user khác nhau, sau đó thực hiện tất cả chúng một lần trong một transaction duy nhất mà thôi.
Bằng việc này, ta có thể tiết kiệm được rất nhiều bằng cách không phải lặp đi lặp lại việc trả một lượng fixed 21,000 gas để gửi mỗi giao dịch, hơn thế nữa còn có thể giảm được phí khi thực hiện cold storage accesses (truy cập cùng một storage ở lần sau sẽ rẻ hơn truy cập nó ở lần đầu).
Ta sẽ update code một chút từ:
contract EntryPoint {
function handleOp(UserOperation op);
// ...
}
sang
contract EntryPoint {
function handleOps(UserOperation[] ops);
// ...
}
Mô hình của chúng ta lúc này sẽ trông như thế này:
Hàm handleOps
mới sẽ thực hiện như sau:
- Với mỗi op, gọi
validateOp
tại wallet mà sender op chỉ định. Loại bỏ các op bị fail validation. - Với mỗi op, gọi
executeOp
tại wallet mà sender op chỉ định, theo dõi xem hết bao nhiêu gas, sau đó trả cho executor lượng ETH để trả phí gas đó.
Có một điều đáng chú ý ở đây là ta sẽ thực hiện một loạt tất cả các bước validation, xong hết sau đó ta mới tiến hành thực hiện execution, thay vì ta thực hiện từng cặp validation-execution liên tiếp nhau.
Điều này rất quan trọng cho việc thực hiện simulation.
Vì nếu ta thực hiện theo từng cặp validation-execution, việc execution trước có thể ảnh hưởng đến storage mà validation sau truy cập, việc này có thể dẫn đến trường hợp như lúc trước ta đã bàn là success khi simulate validation, nhưng lại fail khi thực hiện validation thực tế.
Tương tự, ta cũng muốn tránh (hoặc hạn chế tối đa) luôn cả trường hợp mà validation của op này có liên quan tới validation của op khác trong cùng một bundle.
Vấn đề này có thể được giải quyết bằng cách không lưu trữ nhiều op của cùng một wallet trong cùng một bundle. Khi này do các giới hạn về storage ta đã nói ở bên trên, truy cập storage trong op này sẽ không bị xung đột với truy cập storage trong op khác.
Một lợi ích tuyệt vời khác ở đây là executor có thể có một nguồn income mới!
Executor có thể có cơ hội để kiếm thêm từ Maximal Extractable Value (MEV) bằng cách sắp xếp các user op trong một bundle (thậm chí chèn cả op của chính mình vào) theo một cách có thể sinh ra lợi nhuận.
Giờ đây khi xuất hiện việc đóng gói các user op, ta sẽ dừng việc gọi những người thực hiện giao dịch là executor
nữa, mà ta sẽ gọi họ bằng cái tên thật sự được sử dụng trong định nghĩa của ERC-4337 là bundler
- người đóng gói.
Bundlers as network participants
Trong thiết kế hiện tại của chúng ta, các wallet owner sẽ submit các user operation đến các bundler để hi vọng các bundler sẽ đóng gói operation của họ vào trong một bundle. Việc này giống với transaction thông thường, nơi mà EOA submit các transaction đến các miner, hay block builder, và hi vọng họ sẽ đóng gói transaction của mình vào trong một block. Nên với cùng một kiến trúc, ta hoàn toàn có thể có những lợi ích tương tự cho những thành phần khác nhau tham gia network.
Cũng giống như việc các node thông thường lưu trữ các pending transaction trong một nơi gọi là mempool
và sau đó broadcast chúng đến các node khác, các bundler cũng có thể lưu trữ các validated user operation bên trong một mempool và sau đó broadcast cho những bundler khác. Các bundler có thể validate user op trước khi chia sẻ chúng cho những bundler khác, tiết kiệm thời gian cho bundler khác đỡ phải chạy validation.
Một bundler cũng hoàn toàn có thể là một block builder; khi là một, họ hoàn toàn có thể lựa chọn block để đưa bundle của họ vào, do đó có thể giảm thiểu tối đa, hoặc thậm chí triệt tiêu khả năng transaction bị fail trong execution sau khi đã success trong validation.
Và như bên trên ta đã nói, bundlers và block builder hoàn toàn có thể có thêm lợi ích bằng MEV.