Skip to content

Commit 920c3a0

Browse files
authored
add eoa transaction batching using 7702 (#26)
* add eoa transaction batching using 7702 - Implemented `calldata_for_self_execution` method in the `MinimalAccountTransaction` struct to facilitate self-execution of transactions. - Added `moka` for caching in the execution router for authorization cache * cleanup * fix error message
1 parent f53fbd9 commit 920c3a0

File tree

5 files changed

+73
-9
lines changed

5 files changed

+73
-9
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eip7702-core/src/transaction.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ impl<C: Chain> MinimalAccountTransaction<C> {
221221
Ok((wrapped_calls_json, signature))
222222
}
223223

224+
pub fn calldata_for_self_execution(&self) -> Vec<u8> {
225+
let calls = self.wrapped_calls.calls.clone();
226+
227+
let execute_call = executeCall { calls };
228+
229+
execute_call.abi_encode()
230+
}
231+
224232
/// Execute the transaction directly via bundler client
225233
/// This builds the transaction and calls tw_execute on the bundler
226234
pub async fn execute(

server/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ utoipa-axum = "0.2.0"
4242
utoipa-scalar = { version = "0.3.0", features = ["axum"] }
4343
serde_with = "3.14.0"
4444
aws-arn = "0.3.1"
45+
moka = { version = "0.12.10", features = ["future"] }
46+
engine-eip7702-core = { path = "../eip7702-core" }

server/src/execution_router/mod.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::sync::Arc;
22

3-
use alloy::primitives::U256;
3+
use alloy::primitives::{Address, U256};
44
use engine_aa_core::smart_account::{DeterminedSmartAccount, SmartAccount, SmartAccountFromSalt};
55
use engine_core::{
66
chain::{ChainService, RpcCredentials},
@@ -13,6 +13,7 @@ use engine_core::{
1313
},
1414
transaction::InnerTransaction,
1515
};
16+
use engine_eip7702_core::delegated_account::DelegatedAccount;
1617
use engine_executors::{
1718
eip7702_executor::{
1819
confirm::Eip7702ConfirmationHandler,
@@ -50,6 +51,13 @@ pub struct ExecutionRouter {
5051
pub transaction_registry: Arc<TransactionRegistry>,
5152
pub vault_client: Arc<VaultClient>,
5253
pub chains: Arc<ThirdwebChainService>,
54+
pub authorization_cache: moka::future::Cache<AuthorizationCacheKey, bool>,
55+
}
56+
57+
#[derive(Hash, Eq, PartialEq)]
58+
pub struct AuthorizationCacheKey {
59+
eoa_address: Address,
60+
chain_id: u64,
5361
}
5462

5563
impl ExecutionRouter {
@@ -391,14 +399,53 @@ impl ExecutionRouter {
391399
transactions: &[InnerTransaction],
392400
rpc_credentials: RpcCredentials,
393401
signing_credential: SigningCredential,
394-
) -> Result<(), TwmqError> {
395-
if transactions.len() != 1 {
396-
return Err(TwmqError::Runtime {
397-
message: "EOA execution currently supports only single transactions".to_string(),
398-
});
399-
}
402+
) -> Result<(), EngineError> {
403+
let chain = self
404+
.chains
405+
.get_chain(base_execution_options.chain_id)
406+
.map_err(|e| EngineError::InternalError {
407+
message: format!("Failed to get chain: {}", e),
408+
})?;
409+
410+
let transaction = if transactions.len() > 1 {
411+
let delegated_account = DelegatedAccount::new(eoa_execution_options.from, chain);
412+
let is_minimal_account = self
413+
.authorization_cache
414+
.try_get_with(
415+
AuthorizationCacheKey {
416+
eoa_address: eoa_execution_options.from,
417+
chain_id: base_execution_options.chain_id,
418+
},
419+
delegated_account.is_minimal_account(),
420+
)
421+
.await;
422+
423+
let is_minimal_account =
424+
is_minimal_account.map_err(|e| EngineError::InternalError {
425+
message: format!("Failed to check 7702 delegation: {:?}", e),
426+
})?;
427+
428+
if !is_minimal_account {
429+
return Err(EngineError::ValidationError {
430+
message: "EOA is not a 7702 delegated account. Batching transactions requires 7702 delegation. Please send a 7702 transaction first to upgrade the EOA.".to_string(),
431+
});
432+
}
433+
434+
let calldata = delegated_account
435+
.owner_transaction(transactions)
436+
.calldata_for_self_execution();
437+
438+
InnerTransaction {
439+
to: Some(eoa_execution_options.from),
440+
data: calldata.into(),
441+
gas_limit: None,
442+
transaction_type_data: None,
443+
value: U256::ZERO,
444+
}
445+
} else {
446+
transactions[0].clone()
447+
};
400448

401-
let transaction = &transactions[0];
402449
let eoa_transaction_request = EoaTransactionRequest {
403450
transaction_id: base_execution_options.idempotency_key.clone(),
404451
chain_id: base_execution_options.chain_id,

server/src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::Arc;
1+
use std::{sync::Arc, time::Duration};
22

33
use engine_core::{signer::EoaSigner, userop::UserOpSigner};
44
use thirdweb_core::{abi::ThirdwebAbiServiceBuilder, auth::ThirdwebAuth, iaw::IAWClient};
@@ -77,6 +77,11 @@ async fn main() -> anyhow::Result<()> {
7777
let execution_router = ExecutionRouter {
7878
namespace: config.queue.execution_namespace.clone(),
7979
redis: redis_client.get_connection_manager().await?,
80+
authorization_cache: moka::future::Cache::builder()
81+
.max_capacity(1024 * 1024 * 1024)
82+
.time_to_live(Duration::from_secs(60 * 5))
83+
.time_to_idle(Duration::from_secs(60))
84+
.build(),
8085
webhook_queue: queue_manager.webhook_queue.clone(),
8186
external_bundler_send_queue: queue_manager.external_bundler_send_queue.clone(),
8287
userop_confirm_queue: queue_manager.userop_confirm_queue.clone(),

0 commit comments

Comments
 (0)